Java多线程
多线程
当今的操作系统绝大部分都是基于 多任务 的操作系统;多任务操作系统的最大特点,是可以同时 运行多个程序;由于操作系统支持 时间片 轮换算法,使得用户感觉多个程序在同时运行,似乎有多个CPU在起作用。
运行在操作系统之上的每个应用程序,都会占用一个独立的 进程(process),而 进程内又允许运行多个线程(thread),这意味着一个程序可以同时执行多个任务的功能;在基于线程的多任务而处理环境中, 线程是执行特定任务的可执行代码的最小单位;多线程帮助你写出CPU最大利用率的高效程序,因为空闲时间保持最低,这对Java运行的交互式的网络互连环境是至关重要的,例如:网络的数据传输速率远低于计算机的处理能力,在传统的单线程环境中,你的计算机必须花费大量的空闲时间来等待,多线程能够使你充分利用这些空闲时间。
进程和线程的区别
进程是指系统中正在运行中的应用程序,它拥有自己独立的内存空间;
线程是指进程中一个执行流程,一个进程中允许同时启动多个线程,他们分别执行不同的任务;
线程与进程的主要区别在于:每个进程都需要操作系统为其分配独立的内存地址空间,而同一进程中的所有线程在同一块地址空间中,这些线程可以共享数据,因此线程间的通信比较简单,消耗的系统开销也相对较小。
在Java中实现线程有两种方式
java.lang.Thread
java.lang.Runnable
主线程
任何一个Java程序启动时,一个线程立刻运行,它执行 main 方法,这个线程称为程序的 主线程; 也就是说,任何Java程序都 至少有一个线程 ,即主线程; 主线程的特殊之处在于:
它是产生其它线程子线程的线程;
通常它必须最后结束,因为它要执行其它子线程的关闭工作。
public class MainThreadDemo {
public static void main(String[] args) {
Thread tMain=Thread.currentThread();
System.out.println("当前的线程是:"+tMain);
try{
for(int i=0;i<5;i++){
System.out.println(i);
Thread.sleep(2000);
}
}catch (Exception e) {
e.printStackTrace();
}
}
}
只有Thread.start()才能创建线程,执行线程中的run方法。 如果直接调用run方法,则无法创建线程。
Thread类示例
泡茶的时候可以一边洗杯子,一边烧水。
public class MakeTea extends Thread {
public static void main(String[] args) {
new BoilThread().start();
new WashThread().start();
}
}
class BoilThread extends Thread {
@Override
public void run() {
try {
System.out.println("开始烧水");
Thread.sleep(10000);
System.out.println("水烧开了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class WashThread extends Thread {
@Override
public void run() {
try {
for (int i = 1; i <= 5; i++) {
System.out.println("开始洗第" + i + "个茶杯");
Thread.sleep(1500);
System.out.println("第" + i + "个茶杯洗干净了");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Runnable接口
public class MakeTeaRunnable {
public static void main(String[] args) {
new Thread(new BoilThreadRunnable()).start();
new Thread(new WashThreadRunnable()).start();
}
}
class BoilThreadRunnable implements Runnable {
@Override
public void run() {
try {
System.out.println("开始烧水");
Thread.sleep(10000);
System.out.println("水烧开了");
} catch (Exception e) {
e.printStackTrace();
}
}
}
class WashThreadRunnable implements Runnable {
@Override
public void run() {
try {
for (int i = 1; i < 5; i++) {
System.out.println("开始烧" + i + "杯水");
Thread.sleep(1500);
System.out.println("第" + i + "个被子洗干净了");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
线程同步
多线程共享内存时,如果其中有一个线程对数据进行修改,那么可能出现数据不一致的情况,这时就需要使用同步来解决这个问题。 需要同步的场景:
至少有一个共享数据
至少有一个线程对数据进行修改
同步锁的实现
同步块:(同步块内容一定不能太多,影响运行效率)
只有 引用类型 才能加同步锁。(基础类型的 包装类 也不能加同步锁,因为装箱类型和字符串的值都具有不可变性。意思是:改变值就会使内存地址跟着改变。内存地址改变会使同步锁就会失去共享对象,从而失效。)
多线程的通讯
通过对共享数据进行 wait() 和 notify() ,来达到多线程之间的相互牵制。
wait():让本线程进入等待状态,同时释放同步锁。
notify():通知等待状态的线程,继续执行。
notifyAll():通知所有等待状态的线程,继续执行。
wait和sleep的区别 :·
- wait会释放同步锁,sleep不会。在我们生产者和消费者案例里头,如果使用sleep,不管生产者是否生产出商品,消费者都要睡指定长的时间。但如果使用wait,只要生产者生产出商品就可以立即执行。
- wait是Object的方法,sleep是Thread的静态方法。
计算机只有一个CPU,各个线程 轮流获得CPU 的使用权,才能执行任务;优先级较 高 的线程有 更多 获得CPU的机会,反之亦然;优先级用 整数 表示,取值范围是 1~10 ,一般情况下,线程的默认优先级都是 5 ,但是也可以通过 setPriority 和 getPriority 方法来设置或返回优先级;
多线程死锁
两个线程各自等待对方的资源,并且不释放对方想要的资源。
线程等待
两个线程,一个线程需要等待另一个线程执行后再执行。
线程状态
新建状态:使用new关键字创建线程对象,仅仅被分配了内存;
就绪状态:线程对象被创建后,等待它的start方法被调用,以获得CPU的使用权;
运行状态:执行run方法,此时的线程的对象正占用CPU;
睡眠状态:调用sleep方法,线程被暂停,睡眠时间结束后,线程回到就绪状态,睡眠状态的线程不占用CPU;
死亡状态:run方法执行完毕后,线程进入死亡状态;
阻塞状态:线程由于某些事件(如等待键盘输入)放弃CPU,暂停运行,直到线程重新进入就绪状态,才有机会转到运行状态;
sleep、yield和join
sleep和yield都是Thread类的静态方法,都会使当前处于运行状态的线程放弃CPU,但两者的区别在于:
sleep给其它线程运行的机会,但不考虑其它线程的优先级;但yield只会让位给相同或更高优先级的线程;
当线程执行了sleep方法后,将转到阻塞状态,而执行了yield方法之后,则转到就绪状态;
sleep方法有可能抛出异常,而yield则没有;
在一般情况下,我们更建议使用sleep方法。
join方法用于等待其它线程结束,当前运行的线程可以调用另一线程的join方法,当前运行线程将转到阻塞状态,直至另一线程执行结束,它才会恢复运行。