目录
程序、进程、线程:
在操作系统中运行的程序就是进程,一个进程可以有多个线程
线程和进程的区别:
值得注意:我们所接触的多线程是模拟出来的,真正的多线程是在多个CPU中进行,即多核,如服务器,模拟出来的多线程是在一个CPU下进行,在CPU中同一时间只能执行之中代码,因为执行的快,所以就会用同时执行的感觉
线程的核心概念:
- 线程是独立的执行片段
- 在程序运行时,即便是你没有创建线程,后台也会有多个线程存在,如gc线程,主线程(main)
- main()称为主线程,用来执行整个程序
- 如果在一个进程中创建多个线程,线程的运行是由调度器安排的,调度器与操作系统紧密相关,所以线程的顺序不是认为能干预的。
- 对用一份资源使用时,会发生资源抢夺的问题,需要加入并发控制
- 线程会带来额外的开销,如:cup调度时间,并发控制的开销
- 每个线程在自己工作内存交互,加载和存储主内存控制不当会造成数据不一致
创建线程
- 继承Thread类(现实的Runable接口)
- 实现Runnable接口 优先使用Runnable,因为在JAVA中,是单继承,而避免单继承的局限性,还方便我们共享资源
- 实现Callable
package Thread;
public class Test1 {
public static void main(String[] args) {
Test1Test test = new Test1Test();
test.start(); //不是马上执行,而是交给CPU,即我准备好了,啥时候用看你
Test1Test1 test1 = new Test1Test1(); 、//实现类对象
//实现Runable接口必须借助Thread对象, Thread称为代理对象
new Thread(test1).start();
for(int i=0;i<5;i++){
System.out.println("我在看电影");
}
}
}
/**
* 继承Thread类来实现多线程
* 1.创建 2 启动
*/
class Test1Test extends Thread{
@Override
public void run() {
for(int i =0;i<5;i++){
System.out.println("我在听歌");
}
}
}
/**
* 实现Runable接口来实现多线程
*/
class Test1Test1 implements Runnable{
@Override
public void run() {
for(int i = 0; i<5;i++){
System.out.println("我在玩游戏");
}
}
}
注:因为每次CPU处理的不用,所以每次的执行结界不一定相等
例子:用多线程下载图片
package Thread;
/**
* 用继承Thread类的开启多线程下载图片
*/
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
public class Download {
public static void main(String[] args) {
new DowmThread("http://spider.nosdn.127.net/fdcfb76b8e77a120810190438002a34d.jpeg","src/Thread/艾薇儿1.gif").start();
new DowmThread("http://p4.yokacdn.com/pic/people/spotlight/2013/U454P41T8D265160F430DT20131029114943_maxw808.jpg","src/Thread/艾薇儿2.gif").start();
new DowmThread("http://p0.ifengimg.com/pmop/2018/0920/0AAB0969EAEB2FF4FB2AF960F6BA82695BC0C00B_size214_w1024_h768.jpeg","src/Thread/艾薇儿3.gif").start();
}
}
class DowmThread extends Thread{
private String url;
String name;
public DowmThread(String url, String name) {
this.url = url;
this.name = name;
}
@Override
public void run() {
Down.dowwPicture(url,name);
System.out.println(name);
}
}
class Down{
public static void dowwPicture(String url, String name){
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
}
}
}
src/Thread/艾薇儿1.gif
src/Thread/艾薇儿3.gif
src/Thread/艾薇儿2.gif
package Thread;
/**
* 利用Runnable接口实现多窗口买票
* Runnble的优点:方便共享资源
*/
public class ticket {
public static void main(String[] args) {
//一份资源
TicketTest tt = new TicketTest();
//多个代理
new Thread(tt,"码畜").start();
new Thread(tt,"码奴").start();
new Thread(tt,"码农").start();
}
}
class TicketTest implements Runnable{
int ticketcount = 99;
public void run() {
while (true){
if(ticketcount>0){
try {
Thread.sleep(100); //模拟现实网络延迟,那么结果就会出现问题
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"===>"+ticketcount--);
}
else break;
}
}
}
代理对象
例如婚庆公司,虽然主体还是以结婚双方,但婚庆公司参与了协助工作,即结婚双方为真实对象,而婚庆公司为代理对象
package Thread;
/**
* 静态代理
* 1.真实对象
* 2.代理对象
* 俩个对象实现同一个接口
*/
public class StaticPorxy {
public static void main(String[] args) {
new MarryCompony(new Couple()).happyMarry();
/**
* 同实现Runnable接口的开启方法是相一致的
* new Thread(线程对象).start();
*/
}
}
interface Marry{
void happyMarry();
}
//真实对象
class Couple implements Marry{
@Override
public void happyMarry() {
System.out.println("我们结婚啦");
}
}
//代理对象
class MarryCompony implements Marry{
//真实对象
Couple couple;
public MarryCompony(Couple couple) {
this.couple = couple;
}
@Override
public void happyMarry() {
ready();
couple.happyMarry();
after();
}
private void ready(){
System.out.println("准备工作......");
}
private void after(){
System.out.println("我们收钱啦......");
}
}
准备工作......
我们结婚啦
我们收钱啦......
lambda简化线程
package Thread;
public class LambdaThreadTest {
//静态内部类
static class things implements Runnable{
@Override
public void run() {
System.out.println("我在听歌..........");
}
}
public static void main(String[] args) {
//静态内部类
new Thread(new things()).start();
//局部内部类
class things implements Runnable{
@Override
public void run() {
System.out.println("我在听歌.......");
}
}
new Thread(new things()).start();
//匿名类(需要借助接口)
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("我在听歌....");
}
}).start();
//Lamdba表达式(JDK8)
new Thread(()-> {
{
System.out.println("我在听歌.");
}
}).start();
//在简化
new Thread( ()-> System.out.println("我还在听歌") ).start();
}
}
我在听歌..........
我在听歌.......
我在听歌....
我在听歌.
我还在听歌
线程状态
线程方法
- sleep()
- 使线程停止运行一段时间,将处于阻塞状态
- 如果调用了sleep方法之后,没有其他等待执行的线程,这个时候当前线程不会马上恢复执行
- jion()
- 阻塞指定线程等到另一个线程完成以后再继续执行
- yield()
- 让当前正在执行线程暂停,不是阻塞线程,而是将线程转入就绪状态;
- 调用了yield方法之后,如果没有其他等待执行的线程,此时当前线程就会马上恢复执行
- setDeamon()
- 可以将指定的线程设置成后台线程,守护线程;
- 创建用户线程的线程结束时,后台线程也随之消亡;
- 只能在线程启动之前把它设为后台线程
- setPriority(int newPriority) getPriority()
- 线程的优先级代表的是概率
- 范围从1到10,默认为5
- stop()停止线程
- 不推荐使用
线程终止:
- 不使用JDK提供的stop()/destroy()方法(它们本身也被JDK废弃了)。
- 提供一个boolean型的终止变量,当这个变量置为false,则终止线程的运行。
- 线程正常结束完毕——>次数
package Thread;
/**
* 终止线程:
* 1.程序正常执行完毕
* 2.外部干涉 ==》 加入标识
*/
public class StopThread {
public static void main(String[] args) {
StopThreadTest st = new StopThreadTest("纳尔");
new Thread(st).start();
for (int i = 0; i < 50; i++) {
if (i == 49)
st.stop();
System.out.println("我是=====>提莫");
}
}
}
class StopThreadTest implements Runnable {
private String name;
//标识
private boolean flag = true;
public StopThreadTest(String name) {
this.name = name;
}
@Override
public void run() {
while (flag) {
System.out.println("我是=====>" + name);
}
}
public void stop() {
flag = false;
}
}
线程暂停——Sleep
- sleep阻塞当前线程的毫秒数
- sleep存在InterruptedException异常
- sleep时间到达后,线程进入就绪状态
- sleep可以模拟网络延时,倒计时等。(在上面卖票就用的模拟网络延时
- 每个对象都有一个锁,sleep不会释放锁
Yield——礼让
- 礼让线程是让当前的正在执行线程暂停
- 不是阻塞线程,而是将线程从运行状态转到就绪状态
- 让CPU调度器,重新调度
package Thread;
public class Yield {
public static void main(String[] args) {
new Thread(() -> {
for (int i = 0; i < 50; i++){
System.out.println("我是乔峰——"+i);
}
}).start();
for(int i = 0; i <50; i++){
if(i%10 == 0)
Thread.yield();
System.out.println("我是虚竹——"+i);
}
}
}
注:因为礼让线程是让线程从运行状态转到就绪状态,即重新继续和其他线程抢夺CPU的使用权,具体翻谁牌子还是得看CPU
Join——插队
join合并线程,待此线程执 行完成后,再执行其他线 程,其他线程阻塞
package Thread;
public class Jion {
public static void main(String[] args) {
System.out.println("爸爸让儿子买烟的故事...");
new Thread(new JionFather()).start();
}
}
class JionFather implements Runnable {
@Override
public void run() {
System.out.println("想抽烟,没烟了,给儿子钱,让儿子去买");
Thread thread = new Thread(new JionSon());
thread.start();
System.out.println("等待儿子买烟回来....");
try {
thread.join(); //爸爸这个线程就被阻塞了
System.out.println("烟拿回来了,抽上烟的爸爸把儿子胖揍了一顿");
} catch (InterruptedException e) {
System.out.println("儿子走丢了");
}
}
}
class JionSon implements Runnable{
@Override
public void run() {
System.out.println("儿子拿钱去买烟了");
System.out.println("然而先去了网吧");
for(int i = 0; i < 5; i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i+1+"小时过去了.....");
}
System.out.println("赶紧区买烟,回家");
}
}
爸爸让儿子买烟的故事...
想抽烟,没烟了,给儿子钱,让儿子去买
等待儿子买烟回来....
儿子拿钱去买烟了
然而先去了网吧
1小时过去了.....
2小时过去了.....
3小时过去了.....
4小时过去了.....
5小时过去了.....
赶紧区买烟,回家
烟拿回来了,抽上烟的爸爸把儿子胖揍了一顿
线程的状态
- NEW
- 尚未启动的线程的线程状态。
- RUNNABLE (就绪状态和运行状态)
- 一个可运行的线程的线程状态。在运行状态的线程在java虚拟机执行,但它可能在等待其他资源,如处理器的操作系统。
- BLOCKED
- 线程阻塞等待监视器锁的线程状态。在阻塞状态的线程等待监控锁进入一个同步块/方法或进入一个同步块/方法调用后
Object.wait
。
- 线程阻塞等待监视器锁的线程状态。在阻塞状态的线程等待监控锁进入一个同步块/方法或进入一个同步块/方法调用后
- WAITING
- 等待线程的线程状态。一个线程处于等待状态由于调用以下方法之一:
Object.wait
没有超时Thread.join
没有超时LockSupport.park
- 等待线程的线程状态。一个线程处于等待状态由于调用以下方法之一:
- TIMED_WAITING
- 具有指定等待时间的等待线程的线程状态。一个线程在等待状态的时间由于调用下面的方法用指定的正等待时间:
- TERMINATED
- 终止线程的线程状态。线程已完成执行。
package Thread;
public class ThreadState {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(300);
System.out.println("...........");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
System.out.println(thread.getState()); //NEW
thread.start();
System.out.println(thread.getState()); //RUNNABLE
while (!thread.getState().equals(Thread.State.TERMINATED)){
try {
Thread.sleep(200);
System.out.println(thread.getState()); //TIMED_WAITING
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(thread.getState()); //TERMINATED
}
}
优先级(Priority)
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程。线程调 度器按照线程的优先级决定应调度哪个线程来执行。 线程的优先级用数字表示,范围从1到10
• Thread.MIN_PRIORITY = 1
• Thread.MAX_PRIORITY = 10
• Thread.NORM_PRIORITY = 5
使用下述方法获得或设置线程对象的优先级。
- int getPriority()
- void setPriority(int newPriority);
package Thread;
public class ThreadPriority {
public static void main(String[] args) {
Thread thread1 = new Thread(new ThreadPriorityTest(),"码畜");
Thread thread2 = new Thread(new ThreadPriorityTest(),"码奴");
Thread thread3 = new Thread(new ThreadPriorityTest(),"码农");
Thread thread4 = new Thread(new ThreadPriorityTest(),"IT民工");
Thread thread5 = new Thread(new ThreadPriorityTest(),"IT工程师");
Thread thread6 = new Thread(new ThreadPriorityTest(),"IT人才");
//设置优先级
thread1.setPriority(Thread.MIN_PRIORITY); //1
thread2.setPriority(Thread.MIN_PRIORITY); //1
thread3.setPriority(Thread.MIN_PRIORITY); //1
thread4.setPriority(Thread.MAX_PRIORITY); //10
thread5.setPriority(Thread.MAX_PRIORITY); //10
thread6.setPriority(Thread.MAX_PRIORITY); //10
thread1.start();
thread2.start();
thread3.start();
thread4.start();
thread5.start();
thread6.start();
}
}
class ThreadPriorityTest implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getPriority()+"======>"+Thread.currentThread().getName());
}
}
优先级的设定建议在start()调用前
注意:优先级低只是意味着获得调度的概率低。并不是绝对先调用优先级高后调 用优先级低的线程。
守护线程(Deamon)
- 线程分为用户线程和守护线程
- 虚拟机必须要确保用户线程执行完步
- 虚拟机不需要等待守护线程执行完毕
- 如后台操作日志、监控内存使用
package Thread;
public class ThreadDeamon {
public static void main(String[] args) {
ThreadDeamonMan tdm = new ThreadDeamonMan();
Thread thread1 = new Thread(tdm);
thread1.start();
}
}
class ThreadDeamonMan implements Runnable {
private int i=0;
@Override
public void run() {
while (true){
i++;
System.out.println(i);
}
}
}
多线程_并发_不同步三大经典案例
1.卖票
package Thread;
/**
*
*/
public class ticket {
public static void main(String[] args) {
//一份资源
TicketTest tt = new TicketTest();
//多个代理
new Thread(tt,"码畜").start();
new Thread(tt,"码奴").start();
new Thread(tt,"码农").start();
}
}
class TicketTest implements Runnable{
int ticketcount = 10;
boolean flag =true;
public void run() {
while (flag){
vest();
}
}
public void vest(){
if(ticketcount<=0) {
flag = false;
return;
}else {
try {
Thread.sleep(100); //模拟现实网络延迟,那么结果就会出现问题
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"===>"+ticketcount--);
}
}
}
2。取钱
package Thread;
/**
* 线程不安全:取钱
*/
public class ThreadGetMoney {
public static void main(String[] args) {
Bank bank = new Bank("喜酒钱",100);
Thread you = new Thread(new ThreadGetMoneyTest(bank,80));
Thread he = new Thread(new ThreadGetMoneyTest(bank,70));
you.start();
he.start();
}
}
class Bank{
String name;
double money;
public Bank(String name, double money) {
this.name = name;
this.money = money;
}
}
class ThreadGetMoneyTest implements Runnable{
Bank bank;
double needMoney; //要取的钱
double pocketMoney; //口袋里的钱
public ThreadGetMoneyTest(Bank bank, double needMoney) {
this.bank = bank;
this.needMoney = needMoney;
}
@Override
public void run() {
if(bank.money - needMoney < 0.0){
return;
}
try {
Thread.sleep(1000); //取钱所用的时间
bank.money -= needMoney;
pocketMoney += needMoney;
System.out.println(Thread.currentThread().getName()+"取的钱数"+needMoney);
System.out.println(Thread.currentThread().getName()+"口袋里的钱"+pocketMoney);
System.out.println("账户里的钱"+bank.money);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3.操作容器
package Thread;
/**
* 线程不安全,操作容器(会存在数组的覆盖)
*/
import java.util.ArrayList;
import java.util.List;
public class ThreadArray {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for(int i = 0; i < 10000; i++){
new Thread(() -> {
list.add(Thread.currentThread().getName());
}).start();
}
try {
Thread.sleep(1000);
System.out.println(list.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
线程同步
并发:同一个对象被多个线程用时操作
现实生活中,我们会遇到“同一个资源,多个人都想使用”的问题。 比 如:派发礼品,多个人都想获得。天然的解决办法就是,在礼品前,大 家排队。前一人领取完后,后一人再领取。处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改 这个对象。 这时候,我们就需要用到“线程同步”。 线程同步其实就 是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕后,下一个线程再使用。
由于同一进程的多个线程共享同一块存储空间。为了保证数据在方法中被访问时的正确性,在访问 时加入锁机制(synchronized),当一个线程获得对象的排它锁,独占资源, 其他线程必须等待,使用后释放锁即可。存在以下问题:
- 一个线程持有锁会导致其它所有需要此锁的线程挂起
- 在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时, 引起性能问题;
- 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级 倒置,引起性能问题
中间有一部分内容,电脑死机了,没了,哎。先留着吧
synchronized关键字
- synchronized 方法
- synchronized 块
¥¥同步方法
public synchronized void method(int args) {}
synchronized 方法控制对“成员变量|类变量”对象的访问:每个 对象对应一把锁,每个 synchronized 方法都必须获得调用该方法 的对象的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占 该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获 得该锁,重新进入可执行状态。
缺陷:若将一个大的方法声明为synchronized 将会大大影响效率。
eg.以前面三大经典中的卖票为例
package Thread;
/**
* 同步方法
*/
public class ticket {
public static void main(String[] args) {
//一份资源
TicketTest tt = new TicketTest();
//多个代理
new Thread(tt,"码畜").start();
new Thread(tt,"码奴").start();
new Thread(tt,"码农").start();
}
}
class TicketTest implements Runnable{
int ticketcount = 10;
boolean flag =true;
public void run() {
while (flag){
vest();
}
}
//同步方法
public synchronized void vest(){
if(ticketcount<=0) {
flag = false;
return;
}else {
try {
Thread.sleep(100); //模拟现实网络延迟,那么结果就会出现问题
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"===>"+ticketcount--);
}
}
}
¥¥ 同步块:
- synchronized (obj){ }, obj称之为同步监视器
- obj可以是任何对象,但是推荐使用共享资源作为同步监视器
- 同步方法中无需指定同步监视器,因为同步方法的同步监 视器是this即该对象本身,或class即类的模子
- 同步监视器的执行过程
- 第一个线程访问,锁定同步监视器,执行其中代码
- 第二个线程访问,发现同步监视器被锁定,无法访问
- 第一个线程访问完毕,解锁同步监视器
- 第二个线程访问,发现同步监视器未锁,锁定并访问
eg.以前面三大经典中的操作容器为例
package Thread;
/**
*
* 使用同步块实现线程安全
*/
import java.util.ArrayList;
import java.util.List;
public class ThreadArray {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for(int i = 0; i < 10000; i++){
new Thread(() -> {
//同步块
synchronized (list) {
list.add(Thread.currentThread().getName());
}
}).start();
}
try {
Thread.sleep(1000);
System.out.println(list.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
eg.以前面三大经典中的取钱为例
package Thread;
public class ThreadGetMoney {
public static void main(String[] args) {
Bank bank = new Bank("喜酒钱",100);
Thread you = new Thread(new ThreadGetMoneyTest(bank,80));
Thread he = new Thread(new ThreadGetMoneyTest(bank,70));
you.start();
he.start();
}
}
class Bank{
String name;
double money;
public Bank(String name, double money) {
this.name = name;
this.money = money;
}
}
class ThreadGetMoneyTest implements Runnable{
Bank bank;
double needMoney; //要取的钱
double pocketMoney; //口袋里的钱
public ThreadGetMoneyTest(Bank bank, double needMoney) {
this.bank = bank;
this.needMoney = needMoney;
}
@Override
public synchronized void run() {
test();
}
//目标应该锁定bank
public void test(){
//提高性能
if(bank.money <= 0){
return;
}
synchronized (bank) {
if (bank.money - needMoney < 0.0) {
return;
}
try {
Thread.sleep(1000); //取钱所用的时间
bank.money -= needMoney;
pocketMoney += needMoney;
System.out.println(Thread.currentThread().getName() + "取的钱数" + needMoney);
System.out.println(Thread.currentThread().getName() + "口袋里的钱" + pocketMoney);
System.out.println("账户里的钱" + bank.money);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
性能分析
package Thread;
/**
*
*/
public class ticket {
public static void main(String[] args) {
//一份资源
TicketTest tt = new TicketTest();
//多个代理
new Thread(tt,"码畜").start();
new Thread(tt,"码奴").start();
new Thread(tt,"码农").start();
}
}
class TicketTest implements Runnable{
int ticketcount = 10;
boolean flag =true;
public void run() {
while (flag){
vest5();
}
}
//尽可能锁定合理的范围(不是指代码,指的是代码的完整性)
public void vest5(){
if (ticketcount <= 0) { //考虑到的是没有票的情况(没有票的情况很多)
flag = false;
return; //这被称作双重检测
}
synchronized(this) {
if (ticketcount <= 0) { //考虑最后一张票
flag = false;
return;
}
try {
Thread.sleep(100); //模拟现实网络延迟,那么结果就会出现问题
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "===>" + ticketcount--);
}
}
//线程不安全,范围太小锁不住
public void vest4(){
synchronized(this) {
if (ticketcount <= 0) {
flag = false;
return;
}
}
try {
Thread.sleep(100); //模拟现实网络延迟,那么结果就会出现问题
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "===>" + ticketcount--);
}
//线程不安全 ticketcount对象在变
//synchronized是要锁一个不变的对象
public void vest3(){
synchronized((Integer)ticketcount) {
if (ticketcount <= 0) {
flag = false;
return;
} else {
try {
Thread.sleep(100); //模拟现实网络延迟,那么结果就会出现问题
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "===>" + ticketcount--);
}
}
}
//同步块 范围太大——》性能底下
public void vest2(){
synchronized(this) {
if (ticketcount <= 0) {
flag = false;
return;
} else {
try {
Thread.sleep(100); //模拟现实网络延迟,那么结果就会出现问题
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "===>" + ticketcount--);
}
}
}
//线程安全:同步方法
public synchronized void vest1(){
if(ticketcount<=0) {
flag = false;
return;
}else {
try {
Thread.sleep(100); //模拟现实网络延迟,那么结果就会出现问题
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"===>"+ticketcount--);
}
}
}
线程协作(cooperation)
应用场景:生产者和消费者问题
- 假设仓库中只能存放一件产品,生产者将生产出来的产品放入 仓库,消费者将仓库中产品取走消费
- 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生 产并等待,直到仓库中的产品被消费者取走为止
- 如果仓库中放有产品,则消费者可以将产品取走消费,否则停 止消费并等待,直到仓库中再次放入产品为止。
分析:这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消 费者之间相互依赖,互为条件
- 对于生产者,没有生产产品之前,要通知消费者等待。而生产了产品之后, 又需要马上通知消费者消费
- 对于消费者,在消费之后,要通知生产者已经消费结束,需要继续生产新 产品以供消费
- 在生产者消费者问题中,仅有synchronized是不够的
- synchronized可阻止并发更新同一个共享资源,实现了同步
- synchronized不能用来实现不同线程之间的消息传递(通信)
解决方法
- 管程法
- 生产者:负责生产数据的模块(这里模块可能是:方法、对象、线程、进程);
- 消费者:负责处理数据的模块(这里模块可能是:方法、对象、线程、进程);
- 缓冲区:消费者不能直接使用生产者的数据,它们之间有个“缓冲区”; 生产者将生产好的数据放入“缓冲区”,消费者从“缓冲区”拿要处理的数据
- 信号灯法(标记法)
package Thread.day2;
import javax.swing.plaf.metal.MetalBorders;
/**
* 协作模型:生产者和消费者——管程法
* 借助数组(管道)
*/
//模拟生产者、消费者、和包子之间的故事
public class test0 {
public static void main(String[] args) {
Buffer buffer = new Buffer();
new Thread(new Producer(buffer)).start();
new Thread(new Customer(buffer)).start();
}
}
//生产者
class Producer implements Runnable {
SteamedBun steamedBun;
Buffer buffer;
public Producer(Buffer buffer) {
this.buffer = buffer;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 5 == 0) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("生产者正在生产第===" + i + "个包子");
buffer.push(new SteamedBun(i));
}
}
}
//消费者
class Customer implements Runnable {
Buffer buffer;
public Customer(Buffer buffer) {
this.buffer = buffer;
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("消费者买了第===" + buffer.get().getId() + "个包子");
}
}
}
//缓存区,也就是控制我生产的量
class Buffer {
int count = 0; //计数器
SteamedBun[] buffer = new SteamedBun[11];
//生产馒头
public synchronized void push(SteamedBun steamedBun) {
if (count == 10) {
try {
//容器已满,等候消费者处理
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
buffer[count++] = steamedBun;
//容器里又加入有馒头,提醒消费者前来处理
this.notifyAll();
}
//获取馒头
public synchronized SteamedBun get() {
if (count == 0) {
try {
//容器里没有馒头,只得等待
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//容器里的馒头没有满,唤醒生产者继续生产
this.notifyAll();
return buffer[--count];
}
}
//馒头
class SteamedBun {
private int id;
public SteamedBun(int id) {
this.id = id;
}
public int getId() {
return id;
}
public SteamedBun() {
}
}
下面这个例子不仅描述了信号灯法,而且告诉我们,搞好一件事情的重要性
package Thread.day2;
/**
* 生产者与消费者模型——信号灯法
* 设置标志位
*/
public class test {
public static void main(String[] args) {
Kungfu kungfu = new Kungfu();
new Thread(new Attack(kungfu)).start();
new Thread(new Defense(kungfu)).start();
}
}
//进攻者
class Attack implements Runnable{
Kungfu kungfu;
private String[] kungfus = {"打狗棒","葵花点穴手","降龙十八掌","黯然销魂掌","玉女心经"};
public Attack(Kungfu kungfu) {
this.kungfu = kungfu;
}
@Override
public void run() {
for(int i =0; i < 10; i++){
kungfu.attack(kungfus[(int)(Math.floor(Math.random()*kungfus.length))]);
}
}
}
//防守者
class Defense implements Runnable{
Kungfu kungfu;
public Defense(Kungfu kungfu) {
this.kungfu = kungfu;
}
@Override
public void run() {
for(int i =0; i < 10; i++){
kungfu.defense();
}
}
}
//武功
class Kungfu {
//招式
String sonicSlash;
boolean flag = true;
public synchronized void attack(String sonicSlash){
if(!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("进攻者使用出了招式===》"+sonicSlash);
//唤醒防守者防守
this.notifyAll();
flag = !flag;
}
public synchronized void defense(){
if(flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("防守者使出了龟壳神功,并大笑到,谁能伤我");
//唤醒防守者防守
this.notifyAll();
flag = !flag;
}
}
进攻者使用出了招式===》黯然销魂掌
防守者使出了龟壳神功,并大笑到,谁能伤我
进攻者使用出了招式===》葵花点穴手
防守者使出了龟壳神功,并大笑到,谁能伤我
进攻者使用出了招式===》打狗棒
防守者使出了龟壳神功,并大笑到,谁能伤我
进攻者使用出了招式===》黯然销魂掌
防守者使出了龟壳神功,并大笑到,谁能伤我
进攻者使用出了招式===》打狗棒
防守者使出了龟壳神功,并大笑到,谁能伤我
进攻者使用出了招式===》葵花点穴手
防守者使出了龟壳神功,并大笑到,谁能伤我
进攻者使用出了招式===》降龙十八掌
防守者使出了龟壳神功,并大笑到,谁能伤我
进攻者使用出了招式===》降龙十八掌
防守者使出了龟壳神功,并大笑到,谁能伤我
进攻者使用出了招式===》黯然销魂掌
防守者使出了龟壳神功,并大笑到,谁能伤我
进攻者使用出了招式===》打狗棒
防守者使出了龟壳神功,并大笑到,谁能伤我
Java提供了三种解决了线程间信息通信的问题
高级主题
1.任务定时调度
package Thread.day2;
import java.util.Date;
import java.util.TimerTask;
import java.util.Timer;
public class TimerTest {
public static void main(String[] args) {
Timer time = new Timer();
// time.schedule(new Work(),3000); 3秒后执行一次
// time.schedule(new Work(),2000,1000); 重复执行
time.schedule(new Work(),new Date(2050,5,20));
}
}
class Work extends TimerTask {
@Override
public void run() {
for(int i = 0; i < 3; i++){
System.out.println("我爱学习,无法自拔,玩游戏没意思");
}
System.out.println("真香");
}
}
2.quartz的使用
3.HappenBefore(指令重排)
你写的代码很可能根本没按你期望的顺序执行,因为编译器和 CPU 会尝 试重排指令使得代码更快地运行。
执行代码的顺序可能与编写代码不一致,即虚拟机优化代码顺序,则为指令重排 happen-before 即:编译器或运行时环境为了优化程序性能而采取的对指令进行重新 排序执行的一种手段
- 在虚拟机层面,为了尽可能减少内存操作速度远慢于CPU运行速度所带来的CPU空置的影 响,虚拟机会按照自己的一些规则(这规则后面再叙述)将程序编写顺序打乱——即写在 后面的代码在时间顺序上可能会先执行,而写在前面的代码会后执行——以尽可能充分 地利用CPU。拿上面的例子来说:假如不是a=1的操作,而是a=new byte[1024*1024](分 配1M空间),那么它会运行地很慢,此时CPU是等待其执行结束呢,还是先执行下面那句 flag=true呢?显然,先执行flag=true可以提前使用CPU,加快整体效率,当然这样的前 提是不会产生错误(什么样的错误后面再说)。虽然这里有两种情况:后面的代码先于前 面的代码开始执行;前面的代码先开始执行,但当效率较慢的时候,后面的代码开始执 行并先于前面的代码执行结束。不管谁先开始,总之后面的代码在一些情况下存在先结 束的可能。
- 在硬件层面,CPU会将接收到的一批指令按照其规则重排序,同样是基于CPU速度比缓存 速度快的原因,和上一点的目的类似,只是硬件处理的话,每次只能在接收到的有限指 令范围内重排序,而虚拟机可以在更大层面、更多指令范围内重排序。
数据依赖
如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个 操作之间就存在数据依赖。数据依赖分下列三种类型:
- 写后读 a = 1;b = a; 写一个变量之后,再读这个位置。
- 写后写 a = 1;a = 2; 写一个变量之后,再写这个变量。
- 读后写 a = b;b = 1; 读一个变量之后,再写这个变量
上面三种情况,只要重排序两个操作的执行顺序,程序的执行结果将会被改 变。所以,编译器和处理器在重排序时,会遵守数据依赖性,编译器和处理 器不会改变存在数据依赖关系的两个操作的执行顺序。
package Thread.day2;
public class HappenBefore {
//变量1
private static int a = 0;
//变量2
private static boolean flag = false;
public static void main(String[] args) throws InterruptedException {
Thread t1,t2;
for(int i = 0; i < 100; i++) {
a = 0;
flag = false;
//线程1:更改数据
t1 = new Thread(() -> {
a = 1;
flag = true;
});
//线程2:读取数据
t2 = new Thread(() -> {
if (flag) {
a *= 1;
}
//存在指令重排
if (a == 0)
System.out.println("happpen Before ===>" + a);
});
t1.start();
t2.start();
//合成线程
t1.join();
t2.join();
}
}
}
happpen Before ===>0
happpen Before ===>0
happpen Before ===>0
happpen Before ===>0
happpen Before ===>1
happpen Before ===>0
happpen Before ===>0
happpen Before ===>1
happpen Before ===>1
4.volitale
volatile保证线程间变量的可见性,简单地说就是当线程A对变量X进行了修改后,在线 程A后面执行的其他线程能看到变量X的变动,更详细地说是要符合以下两个规则:
- 线程对变量进行修改之后,要立刻回写到主内存
- 线程对变量读取的时候,要从主内存中读,而不是缓存。
各线程的工作内存间彼此独立、互不可见,在线程启动的时候,虚拟机为每个内存分配一 块工作内存,不仅包含了线程内部定义的局部变量,也包含了线程所需要使用的共享变量 (非线程内构造的对象)的副本,即为了提高执行效率。
volatile是不错的机制,但是volatile不能保证原子性。
5.单例模式(DCL)
- double-checking
- volitate
- synchronized
package Thread.day3;
/**
* DCL单例模式:套路==》在多线程环境下,对外存在一个对象
* 1.构造器私有化——》避免外部new对象
* 2.提供私有的静态属性——》存储对象的地址
* 3.提供共有的静态方法——》获取地址
*/
public class Danli {
public static void main(String[] args) {
System.out.println("执行DanliTest的结果:");
new Thread(() ->{
System.out.println(DanliTest.getInstance());
}).start();
System.out.println(DanliTest.getInstance());
System.out.println("执行DanliTest2的结果:");
new Thread(() ->{
System.out.println(DanliTest2.getInstance());
}).start();
System.out.println(DanliTest2.getInstance());
}
}
class DanliTest {
//2.提供私有静态熟悉
private static volatile DanliTest dl;
//没有volatile,其他线程可能会访问一个没有初始化的对象
//1.构造器私有化
private DanliTest() {
}
public static DanliTest getInstance(){
//再次检测(双重检测)
if(null != dl) //避免不必要的同步,已经存在对象
return dl;
synchronized (DanliTest.class) {
if (null == dl)
dl = new DanliTest();
//new一个对象的步骤:1.开辟空间,2.初始化对象信息,3.返回对象的地址给引用
//new对象中初始化对象信息比较耗时,慢,可能会出现指令重排,先返回对象的地址,
//可能会出现A线程还在初始化对象信息,B线程就已经拿走了对象的引用(空的对象)
}
return dl;
}
}
class DanliTest2 {
private static volatile DanliTest2 dl;
private DanliTest2() {
}
public static DanliTest2 getInstance(){
if (null == dl)
dl = new DanliTest2();
return dl;
}
}
执行DanliTest的结果:
Thread.day3.DanliTest@4b9385
Thread.day3.DanliTest@4b9385
执行DanliTest2的结果:
Thread.day3.DanliTest2@2a0b20
Thread.day3.DanliTest2@14827d5
Threadlocal
- 在多线程环境下,每个线程都有自己的数据。一个线程使用自己的局部 变量比使用全局变量好,因为局部变量只有线程自己能看见,不会影响 其他线程
- ThreadLocal能够放一个线程级别的变量,其本身能够被多个线程共享 使用,并且又能够达到线程安全的目的。说白了,ThreadLocal就是想 在多线程环境下去保证成员变量的安全,常用的方法,就是 get/set/initialValue 方法。
- JDK建议ThreadLocal定义为private static
- ThreadLocal最常用的地方就是为每个线程绑定一个数据库连接,HTTP 请求,用户身份信息等,这样一个线程的所有调用到的方法都可以非常 方便地访问这些资源
- Hibernate的Session 工具类HibernateUtil
- 通过不同的线程对象设置Bean属性,保证各个线程Bean对象的独立 性。
package Thread.day3;
/**
* ThreadLocal:每个线程自身的存储本地,局部区域
* get、set、initialValue
*/
public class Threadlocal {
private static ThreadLocal<Integer> th = new ThreadLocal<>();
//更改初始化的值
/* private static ThreadLocal<Integer> th = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return 200;
}
};*/
//java 1.8后
/*private static ThreadLocal<Integer> th = ThreadLocal.withInitial(() ->{return 200;});
private static ThreadLocal<Integer> th = ThreadLocal.withInitial(() -> 200);*/
public static void main(String[] args) {
//获取值
System.out.println(Thread.currentThread().getName()+"===>"+th.get());
//设置值
th.set(99);
System.out.println(Thread.currentThread().getName()+"===>"+th.get());
new Thread(new Myrun()).start();
new Thread(new Myrun()).start();
new Thread(new Myrun()).start();
}
static class Myrun implements Runnable{
@Override
public void run() {
//设置一个1-100的随机数
th.set((int)(Math.random()*100+1));
System.out.println(Thread.currentThread().getName()+"===>"+th.get());
}
}
}
main===>null
main===>99
Thread-2===>31
Thread-0===>69
Thread-1===>57
package Thread.day3;
/**
* ThreadLcoal 分析上下文
* 1.构造器:哪里调用就属于哪里
* 2.run:属于本线程自身
*/
public class Threadlocal1 {
private static ThreadLocal<Integer> th = new ThreadLocal<>();
public static void main(String[] args) {
new Thread(new Myrun1()).start();
}
static class Myrun1 implements Runnable{
//属于main方法的区域
public Myrun1() {
System.out.println(Thread.currentThread().getName()+"===>"+th.get());
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"===>"+th.get());
}
}
}
main===>null
Thread-0===>null
可重复锁
锁作为并发共享数据保证一致性的工具,大多数内置锁都是可重入的,也就是 说,如果某个线程试图获取一个已经由它自己持有的锁时,那么这个请求会立 刻成功,并且会将这个锁的计数值加1,而当线程退出同步代码块时,计数器 将会递减,当计数值等于0时,锁释放。如果没有可重入锁的支持,在第二次 企图获得锁时将会进入死锁状态。可重入锁随处可见
eg:手动实现不可重复锁
package Thread.day3;
/**
* 不可重入锁:所不可以延续使用
*/
public class Lock1 {
public static LockTest lt = new LockTest();
public static void aa() throws InterruptedException {
lt.lock();
bb();
lt.unlock();
}
public static void bb() throws InterruptedException {
lt.lock();
System.out.println("......");
lt.unlock();
}
public static void main(String[] args) throws InterruptedException {
aa();
}
}
//锁
class LockTest {
private boolean flag = false;
//获取锁
public synchronized void lock() throws InterruptedException {
while (flag == true){
wait();
}
flag = true;
}
//释放锁
public synchronized void unlock() throws InterruptedException {
flag = false;
notify();
}
}
eg:手动实现可重复锁
package Thread.day3;
/**
* 可重入锁:所可以延续使用
*/
public class ReLock {
public static LockTest1 lt = new LockTest1();
public static void aa() throws InterruptedException {
lt.lock();
System.out.println(lt.getCount());
bb();
lt.unlock();
Thread.sleep(100);
System.out.println(lt.getCount());
}
public static void bb() throws InterruptedException {
lt.lock();
System.out.println(lt.getCount());
lt.unlock();
System.out.println(lt.getCount());
}
public static void main(String[] args) throws InterruptedException {
aa();
}
}
class LockTest1 {
private boolean flag = false;
private Thread thread = null; //当前线程
private int count = 0; //计数器
public int getCount() {
return count;
}
//获取锁
public synchronized void lock() throws InterruptedException {
Thread t = Thread.currentThread();
while (flag == true && t!=thread){
wait();
}
count++;
thread = t;
flag = true;
}
//释放锁
public synchronized void unlock() throws InterruptedException {
if(Thread.currentThread() == thread){
count--;
if(count == 0){
flag = false;
notify();
thread = null;
}
}
}
}
1
2
1
0
通过封装好的类(ReentrantLockTest)
package Thread.day3;
import java.util.concurrent.locks.ReentrantLock;
/**
* 可重入锁:所可以延续使用
*/
public class ReLock {
public static ReentrantLock lt = new ReentrantLock();
public static void aa() throws InterruptedException {
System.out.println(lt.getHoldCount());
lt.lock();
System.out.println(lt.getHoldCount());
bb();
lt.unlock();
Thread.sleep(100);
System.out.println(lt.getHoldCount());
}
public static void bb() throws InterruptedException {
lt.lock();
System.out.println(lt.getHoldCount());
lt.unlock();
System.out.println(lt.getHoldCount());
}
public static void main(String[] args) throws InterruptedException {
aa();
}
}