进程:正在运行的程序,是系统进行资源分配和调度的独立单位。每一个进程都有它的内存空间和系统资源。
线程:在一个进程内可以执行多个任务。每一个任务就可以看成一个线程。线程是进程的执行单元。是程序使用CPU的最小单位。
一)使用thread类
1、继承thread类
2、重写run方法
3、启动多线程:new对象,调用它的start()方法(不能调用run方法,否则只是当作普通方法调用)
4、start方法只能被调用一次
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws Exception {
MyThread thread1=new MyThread();
thread1.start();
}
二)使用runnable
1、实现Runnable接口
2、重写run方法
3、启动多线程:
3.1创建实现runnable接口的类
3.2创建thread类。把实现runnable接口的类当作形参放入tread类
3.3然后用thread类.start()方法!
public class MyRunable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i+"---"+Thread.currentThread().getName());
}
}
public static void main(String[] args) {
MyRunable runbable1=new MyRunable();
Thread thread1=new Thread(runbable1);
Thread thread2=new Thread(runbable1);
thread1.start();
thread2.start();
}
}
三)使用Callable<V>接口
1、实现Callable接口
Callable的特点:允许有返回值,依赖于依赖与线程池实现(在多线程二的文章中演示)
推荐使用runnable的方式:
1、避免单继承的局限性
2、便于多个线程共享资源!
线程调度模型有两种:
1)分时调度模型,所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片。
2)抢占式调度模型,优先让优先级高的线程使用CPU,如果线程的优先级相同,那么随机选择一个。优先级高的线程,使用CPU的时间片会多一些。
java中采用的就是抢占式调度模型。
常用方法:
getName():得到线程名称。
setName():设置线程名称。
currentThread():返回当前正在执行的线程引用。
getPriority():获取线程优先级。(默认优先级是5)
setPriority():设置线程优先级。(必须在1-10范围内,包含)
Thread.sleep():线程睡眠
join():等待这个线程死亡。 (只有它执行完了,其他线程才能走。)
yield():线程礼让。(让出cpu的使用权,重新抢占cpu,所有它本身还是可能抢占到cpu的)
setDaemon():后台线程。 将此线程标记为daemon线程或用户线程。 当运行的唯一线程都是守护进程线程时,Java虚拟机将退出。线程启动前必须调用此方法。
stop():线程中止(已过时)
interrupt():线程中止。
stop和interrupt的区别
stop
image.png
interrupt
image.png
interrupt:把线程状态中止,并抛出一个InterruptedException
stop()方法在现在JDK中不推荐使用,原因是stop()方法过于暴力,强行把执行到一半的线程终止,可能会引起一些数据不一致的问题。因此在使用stop()方法时需要自行决定线程何时退出!
总结:stop()方法执行后,该线程就停止了,不再继续执行了,但是interrupt()方法执行后,它会终止线程的状态,还会继续执行run方法里面的代码
通常不要用interrupt或者stop方法来中断线程,一般用条件判断结束线程方法。如下图:
image.png
线程的生命周期:
新建:创建线程对象
就绪:该线程有执行资格,但是没有执行权
运行:有执行资格,有执行权。
阻塞:由于一些操作让线程处于了该状态,没有执行资格,没有执行权。而另一些操作可以把它激活,激活后处于就绪状态。
死亡:线程对象变成垃圾,等待被回收。
经典线程安全问题
需求1:三个窗口同时卖100张电影票。
public class MyRunable implements Runnable {
int p = 100;
@Override
public void run() {
while (true) {
if (p > 0) {
try {
// 模拟延时
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
p--;
System.out.println("卖了" + (100 - p) + "张票,还剩" + p + "张" + "----" + Thread.currentThread().getName());
} else {
break;
}
}
}
public static void main(String[] args) {
MyRunable runbable1 = new MyRunable();
Thread thread1 = new Thread(runbable1);
Thread thread2 = new Thread(runbable1);
Thread thread3 = new Thread(runbable1);
thread1.start();
thread2.start();
thread3.start();
}
}
运行上面的代码会发现,会发现重复卖同一张票和超卖的问题。
导致线程安全问题的原因
A:是否是多线程环境
B:是否有共享数据
C:是否有多条语句操作共享数据
一般而言A,B我们都不能去改变。可以改C。将多条语句操作包装成一个整体,只能同时有一个线程执行,在它运行完之前(释放锁之前)不能被其他线程抢占。(原子性)
1、使用同步代码块
@Override
public void run() {
while (true) {
synchronized (this) {
if (p > 0) {
try {
// 模拟延时
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
p--;
System.out.println("卖了" + (100 - p) + "张票,还剩" + p + "张" + "----" + Thread.currentThread().getName());
} else {
break;
}
}
}
}
2、使用同步方法
public class MyRunable implements Runnable {
int p = 100;
int x = 0;
private Object obj=new Object();
@Override
public void run() {
while (true) {
if(x%2==0) {
synchronized (obj) {
if (p > 0) {
try {
// 模拟延时
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
p--;
System.out.println("卖了" + (100 - p) + "张票,还剩" + p + "张" + "----" + Thread.currentThread().getName());
}
}
}else {
sell();
}
x++;
}
}
private synchronized void sell() {
if (p > 0) {
try {
// 模拟延时
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
p--;
System.out.println("卖了" + (100 - p) + "张票,还剩" + p + "张" + "----" + Thread.currentThread().getName());
}
}
这里我们将代码改造了一下,x%2==0的时候走同步代码块,否则走同步方法。同时将同步代码块的锁对象换成了成员变量obj,
会发现上面的代码出现了线程安全问题。因为这里同步代码块的锁对象和同步方法的锁对象不一致了。这里同步代码块的锁对象应该换成this,就不会出现线程安全问题了。
这也表明了同步方法的锁对象时this。
同步静态方法的锁对象呢?是类的class文件(MyRunable.class)
总结:
同步代码块的锁对象:形参。
同步方法的锁对象:this
静态同步方法的锁对象:类的class文件。
如果锁对象是this可以使用同步方法,一般而言使用同步代码块。
同步可以解决安全问题的原因在那个形参上面。那个形参就相当于一把锁,多个线程要使用同一把锁。(这里使用this因为只new了一个MyRunable对象。)
注意事项:同步代码块的锁对象可以是任意对象,但是多个线程必须是同一个锁对象。
好处:同步解决了多线程的安全问题。
坏处:降低了程序的效率。
提示:Collections中有方法可以将线程不安全的集合类,转换成线程安全类的方法。synchronizedList()、synchronizedMap()...
网友评论