一,关于进程
操作系统调度的最小单位其实不是进程,而是线程。
常用的 Windows、Linux等操作系统都采用抢占式多任务,如何调度线程完全由操作系统决定,程序自己不能决定什么时候运行,以及执行多长时间。因为同一个应用程序,既可以有多个进程,也可以有多个线程
进程和线程的区别就是:一个进程可以包含一个或多个线程,但至少有一个线程。
实现多任务的方法:
多进程的缺点:
- 创建的进程要比创建线程开销大,尤其是在Windows系统上
- 进程间的通信比线程间通信要慢,因为线程通信就是读写同一个变量,速度很快。
多进程的优点:
- 多进程稳定性比多线程高,因为在多进程情况下,一个进程奔溃不会影响其他进程,而在多线程情况下,任何一个线程的奔溃读会导致整个进程奔溃。
多线程经常需要读写共享变量,并且需要同步。例如播放电影时,就必须由一个线程播放视频,另一个线程播放音频,两个线程需要协调运行,否则画面和声音就不同步,因此,多线程编程的复杂度高,调试也比较困难。
Java 多线程编程的特点在于:
-
- 多线程模型是 Java 程序最基本的并发模型
- 后续读写网络、数据库、Web开发等都依赖 Java 多线程模型
二,创建多线程
Java 语言内置了多线程的支持,当Java程序启动的时候,实际上是启动了一个JVM进程,然后启动主线程执行main()方法,在main()方法中,我们又可以启动其他线程。
1.继承Thread
class MyThread extends Thread {
@Override
public void run() {
// TODO Auto-generated method stub
System.err.println("线程运行...");
}
}
public class JavaDemo01 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Thread t = new MyThread();
t.start();
}
}
2.实现Runnable
class MyThread2 implements Runnable {
@Override
public void run() {
// TODO Auto-generated method stub
System.err.println("线程运行...");
}
}
public class JavaDemo02 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Thread t = new Thread(new MyThread2());
t.start();
}
}
或:利用lambda
public class JavaDemo03 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Thread t = new Thread(()->{
System.err.println("线程运行");
});
t.start();//线程启动
}
}
三,设置优先级
Thread.setPriority(10);
范围是1-10。优先级高的线程被操作系统调度的优先级较高,操作系统对高优先级线程可能调度更频繁,但是我们绝不能通过设置优先级来确保高优先级的线程一定会先执行。
四,线程状态
- New:新创建的线程,尚未执行;
- Runnable:运行中的线程,正在执行run()方法的java代码;
- Blocked:运行中的线程,因为某些操作被阻塞而挂起;
- Waiting:运行中的线程,因为执行sleep()方法正在计时等待;
- Terminated:运行已终止,因为run()方法执行完毕;
当线程启动后,它可以在Runnable、Blocked、Waiting和Timed Waiting这几个状态时间切换,直到最后变成Terminated状态,线程终止。
线程终止的原因:
- 线程正常终止:run()方法执行到return语句返回
- 线程意外终止:run()方法因为未捕获的异常导致线程终止
- 对某个线程的Therad实例调用stop()方法强制终止(强烈不建议使用)
一个线程还可以等待另外一个线程直到它运行结束,例如:main线程在启动t线程后,可以通过t.join()等待t线程结束后再继续运行。
五,守护线程
守护线程是指为其他线程服务的线程。在JVM中,所有非守护线程都执行完毕后,无论有没有守护线程,虚拟机都会自动退出。在守护线程中,编写代码的时候要注意:守护线程不能持有任何需要关闭的资源,例如打开文件等,因为虚拟机退出时,守护线程没有任何机会来关闭文件,这会导致数据丢失。
六,线程同步
通过例子理解
ex:三个窗口同时卖票
class MyThread14 implements Runnable {
private int ticket = 50;
@Override
public void run() {
// TODO Auto-generated method stub
while(true) {
if(this.ticket> 0) {
System.out.println(Thread.currentThread().getName() + "卖票,ticket = " + this.ticket--);
}else {
System.out.println("*** 票卖光了 ***");
break;
}
}
}
}
public class JavaDemo14 {
public static void main(String[] args) {
// TODO Auto-generated method stub
MyThread14 m = new MyThread14();
new Thread(m,"窗口1").start();
new Thread(m,"窗口2").start();
new Thread(m,"窗口3").start();
}
}
实际产生150张票,有三个实例对象分别进行了实例化操作使得票数改变,可以设为静态或者使用runnable接口只实例化一个对象解决。
class MyThread14 implements Runnable {
private int ticket = 50;
@Override
public void run() {
// TODO Auto-generated method stub
while(true) {
if(this.ticket> 0) {
System.out.println(Thread.currentThread().getName() + "卖票,ticket = " + this.ticket--);
}else {
System.out.println("*** 票卖光了 ***");
break;
}
}
}
}
public class JavaDemo14 {
public static void main(String[] args) {
// TODO Auto-generated method stub
MyThread14 m = new MyThread14();
new Thread(m,"窗口1").start();
new Thread(m,"窗口2").start();
new Thread(m,"窗口3").start();
}
}
但是如果出现网络波动等问题,则会出现同步问题,出现错票等情况。
这里用延迟模拟
class MyThread14 implements Runnable {
private int ticket = 500;
@Override
public void run() {
// TODO Auto-generated method stub
while(true) {
if(this.ticket> 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票,ticket = " + this.ticket--);
}else {
System.out.println("*** 票卖光了 ***");
break;
}
}
}
}
public class Practice {
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
new Thread(m,"窗口1").start();
new Thread(m,"窗口2").start();
new Thread(m,"窗口3").start();
}
解决办法(一)-------synchronized代码块
class MyThread14 implements Runnable {
private int ticket = 500;
@Override
public void run() {
// TODO Auto-generated method stub
while(true) {
synchronized (this) {
if(this.ticket> 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票,ticket = " + this.ticket--);
}else {
System.out.println("*** 票卖光了 ***");
break;
}
}
}
}
}
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
MyThread14 m = new MyThread14();
new Thread(m,"窗口1").start();
new Thread(m,"窗口2").start();
new Thread(m,"窗口3").start();
}
解决办法(二)-------synchronized方法
class MyThread14 implements Runnable {
private int ticket = 50;
public synchronized boolean sale() {
if(this.ticket> 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票,ticket = " + this.ticket--);
return true;
}else {
System.out.println("*** 票卖光了 ***");
return false;
}
}
@Override
public void run() {
// TODO Auto-generated method stub
while(this.sale()) {
;
}
}
}
public class JavaDemo14 {
public static void main(String[] args) {
// TODO Auto-generated method stub
MyThread14 m = new MyThread14();
new Thread(m,"窗口1").start();
new Thread(m,"窗口2").start();
new Thread(m,"窗口3").start();
}
}
解决办法(三)-------lock锁
class Win implements Runnable {
private int ticket = 100;
// true 公平锁,先来先执行,不会出现执行了的重复执行,等待了重复等待
// 1、实例化ReentrantLock
ReentrantLock lock = new ReentrantLock(true);// 默认false
@Override
public void run() {
// TODO Auto-generated method stub
while(true) {
try {
// 2、调用lock方法,也就是获取监视器获得锁
lock.lock();
if(ticket> 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票:" + ticket);
ticket--;
} else {
break;
}
} finally {
// TODO: handle exception
// 3、调用解锁
lock.unlock();
}
}
}
}
public class JavaDemo05 {
public static void main(String[] args) {
// TODO Auto-generated method stub
Win win = new Win();
Thread t1 = new Thread(win);
Thread t2 = new Thread(win);
Thread t3 = new Thread(win);
t1.start();
t2.start();
t3.start();
}
}
synchronized的锁是什么?
-
- 任意对象都可以作为同步锁。所有对象都自动含有单一的锁(监视器)。
- 同步方法的锁:静态方法(类名.class)、非静态方法(this)
- 同步代码块:自己指定,很多时候也是指定为this或类名.class
2. 注意
- 必须确保使用同一个资源的多个线程共用一把锁,非常重要,否则就无法保证共享资源的安全
- 一个线程类中的所有静态方法共用同一把锁(类名.class),所有非静态方法共用同一把锁(this),同步代码块(指定需谨慎)
synchronized 和 Lock的异同?
- 都是解决线程安全问题
- synchronized机制是执行完相应的同步代码代码后,自动释放同步监视器,Lock需要手动加锁和释放锁。
- Lock只有代码块锁,synchronized有代码块所和方法锁
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好,并且具有更好的扩展性(提供了更多的子类)
- 优先使用顺序:Lock ->同步代码块(已经进入了方法体,分配了相应的资源) -> 同步方法(在方法体外)
七,线程的死锁
死锁造成的主要原因就是彼此都互相等待着,等待着对方让出资源,死锁实际上是一种开发中出现的不确定的状态,有的时候代码处理不当就会不定期出现死锁,这是属于正常开发中的调试问题。若干个线程访问同一个资源时,一定要进行同步处理,而过多的同步会造成死锁。
八,线程的通信
通信涉及到的三个方法:
1. wait():一旦执行这个方法,当前线程就进入阻塞状态,并释放同步监视器
2. notify():一旦执行这个方法,就会唤醒被wait()的方法,如果有多个线程在wait(),优先唤醒优先级高的.
3.notifyAll():唤醒所有的等待线程
说明:
- wait()、notify()、notifyAll()三个方法必须是使用在同步方法或同步代码块中
- wait()、notify()、notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器
sleep()和wait()异同?
- 一旦执行方法,都可以使得当前线程进入阻塞状态
- 两个方法的声明的位置不同:Thread类中声明sleep(),Object类中声明的是wait()
- 调用的要求不同:sleep()可以在任何地方调用,wait()必须是在同步代码块或同步方法中调用
- 关于是否释放同步监视器的问题,如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放同步监视器/锁,而wait()会。
- 唤醒方式不同在wait()之后,需要有其他线程notify()或notifyAll()该线程才能继续执行。而在sleep()时,等待一定时间后,线程就会自动醒来,并继续执行下面的代码。
实例商品生产
-
/* * 生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品, * 店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下, * 如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。 * * 分析: * 1、是否是多线程的问题:有生产者、消费者 * 2、是否有线程安全问题:也就是数据共享的问题,这里的产品数量就是共享数据 * 3、如何解决线程安全问题:同步机制(三种办法) * 4、是否涉及到线程的通信:wait();生产者生产的产品到了20就wait(),消费者拿不到产品了也wait() */ class Clerk { private int productCount = 0;//初始默认产品为0 // 生产产品 public synchronized void produceProduct() { // TODO Auto-generated method stub if(productCount< 20) { productCount++; System.out.println(Thread.currentThread().getName() + ":开始生产第" + productCount + "个产品"); notify(); } else { try { wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } // 消费产品 public synchronized void consumerProduct() { // TODO Auto-generated method stub if(productCount> 0) { System.out.println(Thread.currentThread().getName() + ":开始消费第" + productCount + "个产品"); productCount--; notify(); } else { try { wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); }//没有了等待 } } } // 生产者(生产者和消费者共用Clerk) class Producer extends Thread { private Clerk clerk; public Producer(Clerk clerk) { super(); this.clerk = clerk; } @Override public void run() { // TODO Auto-generated method stub System.out.println(Thread.currentThread().getName() + ":开始生产产品..."); while(true) { // 执行生产 try { Thread.sleep(10); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } clerk.produceProduct(); } } } // 消费者(生产者和消费者共用Clerk) class Consumer extends Thread { private Clerk clerk; public Consumer(Clerk clerk) { super(); this.clerk = clerk; } @Override public void run() { // TODO Auto-generated method stub System.out.println(Thread.currentThread().getName() + ":开始消费产品..."); while(true) { // 执行生产 try { Thread.sleep(20); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } clerk.consumerProduct(); } } } public class JavaDemoProductAndCustomer { public static void main(String[] args) { // TODO Auto-generated method stub Clerk clerk = new Clerk(); Producer p1 = new Producer(clerk); p1.setName("生产者1"); Consumer c1 = new Consumer(clerk); c1.setName("消费者1"); p1.start(); c1.start(); } }