一、多线程
程序是静止的,程序必须在内存中运行,在内存中运行的程序就是一个进程。
进程(Process)
a.独立性
进程都拥有自己独立的运行内存,进程与进程之间是相互独立的,
一般来说,进程与进程之间通信是比较难的。
进程的创建开销是比较大的。
b.动态性
程序是静止的,运行中的程序才是动态的,所以进程是动态的,
进程拥有自己的生命周期。
c.并发性
加入系统只有一个CPU
那么同一个时间片实际上内存中只有一个程序在运行
CPU会分时轮询处理内存中的每个进程,因为处理的速度很快
给用户的感觉似乎这些进程都在同时运行,这就是并发。
如果希望操作系统中同一时刻有多个进程并行处理,那就需要多核CPU
有几个CPU就可以同时运行几个程序。
线程(Thread)
一个进程包含了多个线程。
线程的优势:1.线程基本不占用内存空间,就在进程中运行,创建开销低。
2.线程也可以参与并发,可以提高进程的运行效率。
所以多线程的执行会出现随机性。
3.线程之间不仅可以独立,而且线程与线程之间的通信也十分方便
因为多个线程在同一个进程中。
4.每个线程出现了异常并不会影响其他的线程的运行。
5.java对多线程的支持非常好。
所以多线程是很有必要的。
多线程:可以为一个进程创建多个线程,可以为程序的一个资源分配多个线程来处理,可以提高运行的效率。
二,多线程的创建
进程拥有一个主线程可认为就是进程自己, 其他线程就是子线程
a.为一个进程创建多个线程。有三种方式
1.继承Thread类创建一个子线程
myThread.setName("李刚1"); // 给线程取一个名字
/** 拿到当前线程的名称 */
String name = Thread.currentThread().getName();
优点:写法简单,其他都是缺点。
缺点:线程已经继承一个类,再不能继承其他类了。
2.实现Runnable接口创建一个子线程
优点:可以继承其他类,多个Thread对象可以使用同一个target来处理。
3.实现Callable接口创建一个子线程。
优点:可以继承其他类,多个Thread对象可以使用同一个target来处理。
线程可以有执行结果,线程可以把异常往外抛出去 。
三, 线程的生命周期
start方法,开启线程,线程不能直接调用run方法,否则当成普通方法来执行。
// 这几个线程控制方法都过时了,因为他们会引起线程的死锁。
myThread.resume();
myThread.destroy();
myThread.suspend();
新建 线程类对象的创建 new Thread()
就绪 线程对象调用了start()方法后就变成了就绪状态
运行 就绪状态的线程得到了CPU分时就开始运行
阻塞 sleep线程,io阻塞,等待同步锁,等待通知,线程挂起suspend();
死亡 线程的run方法正常执行完成,线程出现了一异常,线程被stop()了。
四, 线程控制,可以通过程序控制多线程的执行(相对的)
a.join线程
每次运行到join的时候,当前线程应该让调用join的线程先运行以后,自己再接着执行。
b.sleep睡眠线程
Thread.sleep(毫秒数)
让当前所在的线程睡眠一段时间,时间到了以后变成就绪状态等待CPU调度继续执行。
c.后台线程
(Daemon Thread) - 精灵线程、守护线程。
后台线程有个特征: 如果所有前台线程都死了,它就会自动立即死亡!
注意:如果所有的前台线程先执行完毕了,那么即使后台线程没有执行完也应该立即死亡。
如果后台线程先执行完毕了,那么后台线程实际上并没有死亡,他要等其他线程都执行完再立即死亡。
如何产生一个后台线程呢?
A。自动方式:后台线程启动的线程本身就是后台线程。
B。手动方式。调用线程的setDaemon(true)即可。
典型地:JDK的GC线程,就是后台线程。
d.yield线程,让步线程
yield是让当前线程让出CPU,自己变成了就绪状态
yield很可能在让出CPU后立即又得到了CPU
e.线程优先级
可以为每个线程设置一个优先级,优先级高的线程在竞争CPU的时候会获得更多的执行机会。
Thread:
public final static int MIN_PRIORITY = 1; // 最低优先级
public final static int NORM_PRIORITY = 5; // 默认优先级
public final static int MAX_PRIORITY = 10; // 最高优先级
myThread1.setPriority(Thread.MAX_PRIORITY); // 10
五,线程安全与线程同步
多个线程在访问同一个资源(竞争资源,共享资源)的时候会出现线程安全问题。
10000
/ \
(你) 线程 (老婆)线程
10000>=10000 10000>=10000
true -> true ->
0 -10000
临界区:多条线程访问、修改竞争资源的代码区,就被称为“临界区”。
解决线程安全的关键:保证任意时刻,最多只有一条线程能进入临界区。
线程同步是为了解决线程安全问题
为了保证线程同步,一共有3种方式:
a. 同步代码块。
synchronized(acct) {
// 同步代码块
}
synchronized后面括号里的对象叫“同步监视器、monitor”。
任何线程进入“同步代码块”之前, 必须先对“同步监视器、monitor”加锁(自动完成)
同步会导致性能下降。
虽然Java的语法,可用任何对象作为同步监视器,但一定要记住:
实际上只能选择“竞争资源”作为同步监视器。
当你正确地选择了“竞争资源”作为同步监视器之后,执行流程如下:
对“竞争资源”加锁 → 进入临界区、访问修改“竞争资源” → 对“竞争资源”解锁
——这是一个通用的设计哲学:
对共享资源加锁 → 修改 → 修改完成后,解锁。其他线程再次进来。
b.同步方法
被synchronized修饰的方法,就是同步方法。
对于同步方法而言,如果是实例方法, 使用this作为同步监视器;
如果是类方法,使用该类本身作为同步监视器。
通常来说,应该在“竞争资源”类的里面定义同步方法。
同步方法、同步代码块的逻辑是完全相同的,区别是同步代码块需要显式指定“竞争资源”作为同步监视器。
同步方法则直接定义定义“竞争资源”的实现类中,此时this(竞争资源)作为同步监视器。
同步方法、同步代码块的解锁,也是自动完成——只要程序离开synchronized修饰的代码块、方法,
此处的离开包括正常结束、因为异常退出,系统会自动释放对同步监视器的锁定。
c. 同步锁(JDK 1.5才有)
显式创建Lock、也会显式看到加锁、释放锁。
Lock是一个接口,该接口提供一个ReentrantLock(可重入锁)实现类。
做法:
(1)将显式锁定义成竞争资源的实例变量,并用final修饰(保证锁不会被替换)
务必保证:每个竞争资源有唯一的、不可替换的锁。
(2) 程序要修改竞争资源之前,先调用显式锁的lock方法执行锁定;
修改完成之后,在finally块中调用unlock方法释放锁定。
六,线程通信
生产者和消费者问题
生产者生成商品,消费者消费商品
生产者和消费者必须解决:1.生产的商品不要过剩. 2.消费的商品不能缺货。
生产者生成商品,通知消费者消费
消费消费商品,通知生产者生产。
线程通信: 首先保证线程安全,就是要线程同步,这是强制性的。
家庭作业: 购票系统 100张票 ,空座位,正在使用的坐位,有很多的人买票,也有很多人下站
a.线程通信的方式1
通过 this.notifyAll()来唤醒所有等待的线程。
通过 this.wait()来暂停自己的线程。
先应该唤醒别人才可以停止自己。
b.线程lock锁实现线程通信
// 定义一个线程锁
private final ReentrantLock lock = new ReentrantLock();
// 通过锁得到一个线程通信对象
private final Condition cont = lock.newCondition();
cont.signalAll(); // 通知所有的爸爸,赶紧存钱
cont.await(); // 自己等待
加锁和解锁动作一定要执行。
程序是静止的,程序必须在内存中运行,在内存中运行的程序就是一个进程。
进程(Process)
a.独立性
进程都拥有自己独立的运行内存,进程与进程之间是相互独立的,
一般来说,进程与进程之间通信是比较难的。
进程的创建开销是比较大的。
b.动态性
程序是静止的,运行中的程序才是动态的,所以进程是动态的,
进程拥有自己的生命周期。
c.并发性
加入系统只有一个CPU
那么同一个时间片实际上内存中只有一个程序在运行
CPU会分时轮询处理内存中的每个进程,因为处理的速度很快
给用户的感觉似乎这些进程都在同时运行,这就是并发。
如果希望操作系统中同一时刻有多个进程并行处理,那就需要多核CPU
有几个CPU就可以同时运行几个程序。
线程(Thread)
一个进程包含了多个线程。
线程的优势:1.线程基本不占用内存空间,就在进程中运行,创建开销低。
2.线程也可以参与并发,可以提高进程的运行效率。
所以多线程的执行会出现随机性。
3.线程之间不仅可以独立,而且线程与线程之间的通信也十分方便
因为多个线程在同一个进程中。
4.每个线程出现了异常并不会影响其他的线程的运行。
5.java对多线程的支持非常好。
所以多线程是很有必要的。
多线程:可以为一个进程创建多个线程,可以为程序的一个资源分配多个线程来处理,可以提高运行的效率。
二,多线程的创建
进程拥有一个主线程可认为就是进程自己, 其他线程就是子线程
a.为一个进程创建多个线程。有三种方式
1.继承Thread类创建一个子线程
myThread.setName("李刚1"); // 给线程取一个名字
/** 拿到当前线程的名称 */
String name = Thread.currentThread().getName();
优点:写法简单,其他都是缺点。
缺点:线程已经继承一个类,再不能继承其他类了。
2.实现Runnable接口创建一个子线程
优点:可以继承其他类,多个Thread对象可以使用同一个target来处理。
3.实现Callable接口创建一个子线程。
优点:可以继承其他类,多个Thread对象可以使用同一个target来处理。
线程可以有执行结果,线程可以把异常往外抛出去 。
三, 线程的生命周期
start方法,开启线程,线程不能直接调用run方法,否则当成普通方法来执行。
// 这几个线程控制方法都过时了,因为他们会引起线程的死锁。
myThread.resume();
myThread.destroy();
myThread.suspend();
新建 线程类对象的创建 new Thread()
就绪 线程对象调用了start()方法后就变成了就绪状态
运行 就绪状态的线程得到了CPU分时就开始运行
阻塞 sleep线程,io阻塞,等待同步锁,等待通知,线程挂起suspend();
死亡 线程的run方法正常执行完成,线程出现了一异常,线程被stop()了。
四, 线程控制,可以通过程序控制多线程的执行(相对的)
a.join线程
每次运行到join的时候,当前线程应该让调用join的线程先运行以后,自己再接着执行。
b.sleep睡眠线程
Thread.sleep(毫秒数)
让当前所在的线程睡眠一段时间,时间到了以后变成就绪状态等待CPU调度继续执行。
c.后台线程
(Daemon Thread) - 精灵线程、守护线程。
后台线程有个特征: 如果所有前台线程都死了,它就会自动立即死亡!
注意:如果所有的前台线程先执行完毕了,那么即使后台线程没有执行完也应该立即死亡。
如果后台线程先执行完毕了,那么后台线程实际上并没有死亡,他要等其他线程都执行完再立即死亡。
如何产生一个后台线程呢?
A。自动方式:后台线程启动的线程本身就是后台线程。
B。手动方式。调用线程的setDaemon(true)即可。
典型地:JDK的GC线程,就是后台线程。
d.yield线程,让步线程
yield是让当前线程让出CPU,自己变成了就绪状态
yield很可能在让出CPU后立即又得到了CPU
e.线程优先级
可以为每个线程设置一个优先级,优先级高的线程在竞争CPU的时候会获得更多的执行机会。
Thread:
public final static int MIN_PRIORITY = 1; // 最低优先级
public final static int NORM_PRIORITY = 5; // 默认优先级
public final static int MAX_PRIORITY = 10; // 最高优先级
myThread1.setPriority(Thread.MAX_PRIORITY); // 10
五,线程安全与线程同步
多个线程在访问同一个资源(竞争资源,共享资源)的时候会出现线程安全问题。
10000
/ \
(你) 线程 (老婆)线程
10000>=10000 10000>=10000
true -> true ->
0 -10000
临界区:多条线程访问、修改竞争资源的代码区,就被称为“临界区”。
解决线程安全的关键:保证任意时刻,最多只有一条线程能进入临界区。
线程同步是为了解决线程安全问题
为了保证线程同步,一共有3种方式:
a. 同步代码块。
synchronized(acct) {
// 同步代码块
}
synchronized后面括号里的对象叫“同步监视器、monitor”。
任何线程进入“同步代码块”之前, 必须先对“同步监视器、monitor”加锁(自动完成)
同步会导致性能下降。
虽然Java的语法,可用任何对象作为同步监视器,但一定要记住:
实际上只能选择“竞争资源”作为同步监视器。
当你正确地选择了“竞争资源”作为同步监视器之后,执行流程如下:
对“竞争资源”加锁 → 进入临界区、访问修改“竞争资源” → 对“竞争资源”解锁
——这是一个通用的设计哲学:
对共享资源加锁 → 修改 → 修改完成后,解锁。其他线程再次进来。
b.同步方法
被synchronized修饰的方法,就是同步方法。
对于同步方法而言,如果是实例方法, 使用this作为同步监视器;
如果是类方法,使用该类本身作为同步监视器。
通常来说,应该在“竞争资源”类的里面定义同步方法。
同步方法、同步代码块的逻辑是完全相同的,区别是同步代码块需要显式指定“竞争资源”作为同步监视器。
同步方法则直接定义定义“竞争资源”的实现类中,此时this(竞争资源)作为同步监视器。
同步方法、同步代码块的解锁,也是自动完成——只要程序离开synchronized修饰的代码块、方法,
此处的离开包括正常结束、因为异常退出,系统会自动释放对同步监视器的锁定。
c. 同步锁(JDK 1.5才有)
显式创建Lock、也会显式看到加锁、释放锁。
Lock是一个接口,该接口提供一个ReentrantLock(可重入锁)实现类。
做法:
(1)将显式锁定义成竞争资源的实例变量,并用final修饰(保证锁不会被替换)
务必保证:每个竞争资源有唯一的、不可替换的锁。
(2) 程序要修改竞争资源之前,先调用显式锁的lock方法执行锁定;
修改完成之后,在finally块中调用unlock方法释放锁定。
六,线程通信
生产者和消费者问题
生产者生成商品,消费者消费商品
生产者和消费者必须解决:1.生产的商品不要过剩. 2.消费的商品不能缺货。
生产者生成商品,通知消费者消费
消费消费商品,通知生产者生产。
线程通信: 首先保证线程安全,就是要线程同步,这是强制性的。
家庭作业: 购票系统 100张票 ,空座位,正在使用的坐位,有很多的人买票,也有很多人下站
a.线程通信的方式1
通过 this.notifyAll()来唤醒所有等待的线程。
通过 this.wait()来暂停自己的线程。
先应该唤醒别人才可以停止自己。
b.线程lock锁实现线程通信
// 定义一个线程锁
private final ReentrantLock lock = new ReentrantLock();
// 通过锁得到一个线程通信对象
private final Condition cont = lock.newCondition();
cont.signalAll(); // 通知所有的爸爸,赶紧存钱
cont.await(); // 自己等待
加锁和解锁动作一定要执行。