目录
并发:多线程抢占 CPU,可能不同时执行,侧重于多个任务交替执行。
并行:线程可以不共享 CPU,可每个线程一个 CPU 同时执行多个任务
1. 进程与线程认知强化
1.1如何理解进程与线程?
首先我们先来了解一下关于线程的一些基本的定义:
进程:
进程:操作系统进行资源调度和分配的基本单位(例如浏览器,APP,JVM)。
1.进程是系统资源分配的最小单位
2.一个程序拥有一个进程而一个进程可以有多个线程
线程:
线程:进程中的最小执行单位,是 CPU 资源的分配的基本单位(可以理解为一个顺序的执行流)。
1.线程是一个程序的最小执行单位
2.并发就是在单核处理器中同时处理多个任务
3.并行就是在多核处理器中同时处理多个任务
1.2如何理解多线程中的并发与并行?
并发:多线程抢占 CPU,可能不同时执行,侧重于多个任务交替执行。
并行:线程可以不共享 CPU,可每个线程一个 CPU 同时执行多个任务
1.3如果理解线程的声明周期及状态变化?
2. 线程并发安全问题认知强化
2.1. 如何理解线程安全与不安全?
public class TestThread implements Runnable{
int ticket=30;
public void run(){
doTicket();
}
private void doTicket() {
while (true){
if (ticket<=0)break;
System.out.println(ticket--);
//让线程阻塞一下
sleep();
}
}
public void sleep(){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
//几核cpu
Runtime r=Runtime.getRuntime();
System.out.println("几核cpu"+r.availableProcessors());
//创建卖票任务
TestThread task=new TestThread();
//多线程模拟多个售票窗口
Thread t1=new Thread(task);
Thread t2=new Thread(task);
Thread t3=new Thread(task);
Thread t4=new Thread(task);
//
t1.start();
t2.start();
t3.start();
t4.start();
}
}
结果:
"C:\Program Files\Java\jdk1.8.0_251\bin\java.exe" "-javaagent:E:\Program Files\JetBrains\IntelliJ IDEA 2020.1.4\IntelliJ IDEA 2020.1\lib\idea_rt.jar=52896:E:\Program Files\JetBrains\IntelliJ IDEA 2020.1.4\IntelliJ IDEA 2020.1\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_251\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\rt.jar;C:\Users\Administrator\Desktop\课件\课件\02_oop\10\Day10_all\test\target\classes;C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot-starter\2.4.1\spring-boot-starter-2.4.1.jar;C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot\2.4.1\spring-boot-2.4.1.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-context\5.3.2\spring-context-5.3.2.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-aop\5.3.2\spring-aop-5.3.2.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-beans\5.3.2\spring-beans-5.3.2.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-expression\5.3.2\spring-expression-5.3.2.jar;C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot-autoconfigure\2.4.1\spring-boot-autoconfigure-2.4.1.jar;C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot-starter-logging\2.4.1\spring-boot-starter-logging-2.4.1.jar;C:\Users\Administrator\.m2\repository\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;C:\Users\Administrator\.m2\repository\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;C:\Users\Administrator\.m2\repository\org\apache\logging\log4j\log4j-to-slf4j\2.13.3\log4j-to-slf4j-2.13.3.jar;C:\Users\Administrator\.m2\repository\org\apache\logging\log4j\log4j-api\2.13.3\log4j-api-2.13.3.jar;C:\Users\Administrator\.m2\repository\org\slf4j\jul-to-slf4j\1.7.30\jul-to-slf4j-1.7.30.jar;C:\Users\Administrator\.m2\repository\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-core\5.3.2\spring-core-5.3.2.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-jcl\5.3.2\spring-jcl-5.3.2.jar;C:\Users\Administrator\.m2\repository\org\yaml\snakeyaml\1.27\snakeyaml-1.27.jar;C:\Users\Administrator\.m2\repository\org\slf4j\slf4j-api\1.7.30\slf4j-api-1.7.30.jar" cn.tedu.test.TestThread
几核cpu8
30
28
29
27
26
23
25
24
22
22
22
22
21
21
20
20
19
19
19
19
18
17
18
16
15
14
13
15
12
11
10
9
8
7
6
6
5
5
4
4
3
2
1
Process finished with exit code 0
从结果来看发生了重复,出现这种情况显然表明我们这个方法根本就不是线程安全的,出现这种问题的原因有很多,我们说最常见的一种,就是我们A线程在进入方法后,拿到了trcket的值,刚把这个值读取出来还没有改变trcket的值的时候,结果线程B也进来的,那么导致线程A和线程B拿到的trcket值是一样的。
在并发编程中,这种由于不恰当的执行时序而出现不正确的结果是一种非常重要的情况,它有一个正式的名字,叫“竞态条件”。竞态条件是啥?当某个计算的正确性取决于多个线程的交替执行时序时,那么就会发生竞态条件,最常见的竞态条件类型就是“先检查后执行”操作,即通过一个可能失效的观察结果来决定下一步的动作。比如首先观察到某个条件为真(例如文件X不存在),然后根据这个观察结果采用相应的动作(创建文件X),但事实上,在你观察到这个结果以及开始创建文件之间,观察结果可能变得无效(另一个线程在这个期间创建了文件X),从而导致各种问题(未预期的异常、数据覆盖、文件被破坏等)。
与大多数并发错误一样,竞态条件并不总是会产生错误,还需要某种不恰当的执行时序。原子操作是对于访问同一个状态的所有操作(包括该操作本身)来说,这个操作是一个以原子方式执行的操作。而我们前面提到的类似trcket--的这种操作,叫做复合操作,即包含了一组必须以原子方式执行的操作以确保线程安全性。在实际情况中,应该尽可能的使用现有的线程安全对象来管理类的状态,与非线程安全的对象相比,判断线程安全对象的可能状态及其状态转换情况要更为容易,从而也更容易维护和验证线程安全性。这里值得一提的是,java中给我们弄好了很多原子操作的类型,在这个包下java.util.concurrent.atomic。
我们要保证线程的安全性
加锁时我们要保证操作的原子性(其实原子性就是一个不可分割的)
原子性我们举一个例子:
A想要从自己的帐户中转1000块钱到B的帐户里。那个从A开始转帐,到转帐结束的这一个过程,称之为一个事务。在这个事务里,要做如下操作:
1. 从A的帐户中减去1000块钱。如果A的帐户原来有3000块钱,现在就变成2000块钱了。
2. 在B的帐户里加1000块钱。如果B的帐户如果原来有2000块钱,现在则变成3000块钱了。
如果在A的帐户已经减去了1000块钱的时候,忽然发生了意外,比如停电什么的,导致转帐事务意外终止了,而此时B的帐户里还没有增加1000块钱。那么,我们称这个操作失败了,要进行回滚。回滚就是回到事务开始之前的状态,也就是回到A的帐户还没减1000块的状态,B的帐户的原来的状态。此时A的帐户仍然有3000块,B的帐户仍然有2000块。
我们把这种要么一起成功(A帐户成功减少1000,同时B帐户成功增加1000),要么一起失败(A帐户回到原来状态,B帐户也回到原来状态)的操作叫原子性操作。
如果把一个事务可看作是一个程序,它要么完整的被执行,要么完全不执行。这种特性就叫原子性。
修改的代码:
private void doTicket() {//在这里加锁性能低,单个线程使用,不符合原子性 private synchronized void doTicket() {
while (true){
//多线程在此代码上排队执行(同步)
synchronized (this) {//保证操作的原子性,这里this指的是对象
if (ticket <= 0) break;
//获取线程的名字
String tName = Thread.currentThread().getName();
System.out.println(tName + ":" + ticket--);
//让线程阻塞一下
sleep();
}//排他锁(同步锁)
}
}
这里我们可以理解为:加在上面锁的是商场,加在下面锁的是试衣间.我们在保证安全的情况下也要保证性能.
结果为:
"C:\Program Files\Java\jdk1.8.0_251\bin\java.exe" "-javaagent:E:\Program Files\JetBrains\IntelliJ IDEA 2020.1.4\IntelliJ IDEA 2020.1\lib\idea_rt.jar=51922:E:\Program Files\JetBrains\IntelliJ IDEA 2020.1.4\IntelliJ IDEA 2020.1\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_251\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_251\jre\lib\rt.jar;C:\Users\Administrator\Desktop\课件\课件\02_oop\10\Day10_all\test\target\classes;C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot-starter\2.4.1\spring-boot-starter-2.4.1.jar;C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot\2.4.1\spring-boot-2.4.1.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-context\5.3.2\spring-context-5.3.2.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-aop\5.3.2\spring-aop-5.3.2.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-beans\5.3.2\spring-beans-5.3.2.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-expression\5.3.2\spring-expression-5.3.2.jar;C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot-autoconfigure\2.4.1\spring-boot-autoconfigure-2.4.1.jar;C:\Users\Administrator\.m2\repository\org\springframework\boot\spring-boot-starter-logging\2.4.1\spring-boot-starter-logging-2.4.1.jar;C:\Users\Administrator\.m2\repository\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;C:\Users\Administrator\.m2\repository\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;C:\Users\Administrator\.m2\repository\org\apache\logging\log4j\log4j-to-slf4j\2.13.3\log4j-to-slf4j-2.13.3.jar;C:\Users\Administrator\.m2\repository\org\apache\logging\log4j\log4j-api\2.13.3\log4j-api-2.13.3.jar;C:\Users\Administrator\.m2\repository\org\slf4j\jul-to-slf4j\1.7.30\jul-to-slf4j-1.7.30.jar;C:\Users\Administrator\.m2\repository\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-core\5.3.2\spring-core-5.3.2.jar;C:\Users\Administrator\.m2\repository\org\springframework\spring-jcl\5.3.2\spring-jcl-5.3.2.jar;C:\Users\Administrator\.m2\repository\org\yaml\snakeyaml\1.27\snakeyaml-1.27.jar;C:\Users\Administrator\.m2\repository\org\slf4j\slf4j-api\1.7.30\slf4j-api-1.7.30.jar" cn.tedu.test.TestThread
几核cpu8
Thread-0:30
Thread-0:29
Thread-0:28
Thread-0:27
Thread-0:26
Thread-0:25
Thread-0:24
Thread-0:23
Thread-0:22
Thread-0:21
Thread-0:20
Thread-0:19
Thread-0:18
Thread-0:17
Thread-0:16
Thread-0:15
Thread-0:14
Thread-0:13
Thread-3:12
Thread-3:11
Thread-3:10
Thread-3:9
Thread-3:8
Thread-3:7
Thread-3:6
Thread-3:5
Thread-3:4
Thread-3:3
Thread-3:2
Thread-3:1
Process finished with exit code 0
这样我们就能看到 多核排队运行
我们再来设计一个实验:
package cn.tedu.test;
import java.util.ArrayList;
import java.util.List;
public class TestThread04 {
static class Counter implements Runnable{
//禁止指定重排序 volatile
private volatile int count;
@Override
public void run() {
for (int i=0;i<10;i++){
doCount();
}
}
public void doCount() {
count++;//此操作非原子操作 int temp=count;result=temp=1;count=result
}
public int getCount(){
return count;
}
public static void main(String[] args) {
//1.构建计数任务对象
Counter counter = new Counter();
//2.构建多个线程
List<Thread> list = new ArrayList<>();
for (int i = 0;i < 100; i++) {
list.add(new Thread(counter));
}
//3.启动线程
for (Thread t : list) {
t.start();
}
//4.所有线程执行结束,输出counter的值.
//判定线程活着的数量是否大于1
//如果你使用的是idea则包括一个监控线程这里要写2
while (Thread.activeCount() > 1)
Thread.yield();
System.out.println(counter.getCount());
}
}
}
结果:
1000
Process finished with exit code 0
但是他会出现这样的情况:
993
Process finished with exit code 0
解决方法是加 synchronized 保持它的原子操作
public synchronized void doCount() {//this
count++;//此操作非原子操作 int temp=count;result=temp=1;count=result
}
他是把ABC三个线程都执行结束,然后再执行其他操作,保证线程的安全
public synchronized void doCount() {//this
count++;//此操作非原子操作 ABC
系统可能先执行A,然后在执行C最后执行B ,因为JVM底层他会对我们这个指令语句执行的过程进行优化
所以我们加上volatile,他让我们安装顺序执行。
//禁止指定重排序 volatile
private volatile int count;
在使用了volatile保证了顺序和synchronized保证了原子性后我们在测试的时候就没有问题了,都是1000了
现在就是安全的了。
2.2. 导致线程不安全的因素有哪些?
import java.util.Arrays;
class Container {
/*容器*/
private Object[] array;
/*有效元素个数*/
private int size;
Container() {
this(12);
}
public Container(int cap) {
array = new Object[cap];
}
/*
* 像容器size放数据
* */
public synchronized void add(Object data) {
//1判定容器是否已满.满了则扩容
if (size == array.length)
array = Arrays.copyOf(array, 2 * array.length);
//2放数据
array[size] = data;
//3.修改size的值
size++;
}
}
public class Test06 {
public static void main(String[] args) {
Container c = new Container();
for (int i = 0; i < Integer.MAX_VALUE; i++) {
c.add(new byte[1024 * 1024 * 10]);
System.out.println(i);
}
}
}
结果为:
1
2
...
...
173
174
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at cn.tedu.test.Test06.main(Test06.java:38)
Process finished with exit code 1
内存溢出了
我们可以给代码加上同步代码块
synchronized (array) {//同步代码块
...
...
}
2.3. 如何保证并发线程的安全性?
package cn.tedu.test;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Counter01{
private volatile int count;
public synchronized void Count(){
//synchronized在JDK1.6之前性能非常差会采用一些手动加锁
//在1.6后synchronized支持自旋锁,轻量级锁,重量级锁等但是他不支持公平性
//他都是非公平性的,假如使用公平锁就要使用下面的方法
count++;
}//排他锁,独占锁,非公平锁
}
class Counter02{
private volatile int count;
//公平锁,排他锁,独占锁
private Lock lock=new ReentrantLock(true);
public void Count(){
//保证线程的安全在这里加一把锁
//手动加锁能灵活
lock.lock();//加锁
try {
count++;
}finally {
//必须在这里解锁,以避免死锁
lock.unlock();//解锁
}
}
}
//这种方法能保证线程的安全但是不管你怎样加锁都可能会有阻塞,使用在JDk里面又提供了一种方式
class Counter03{
//底层使用CAS算法(基于CPU硬件实现)
private AtomicInteger at=new AtomicInteger();
public void count(){
at.incrementAndGet();
}
}
//上面是一种加锁的状态,但是我们要想让每个线程都有一份,这个就属于线程内部单例了
//单例是要确保一个对象在一定范围内他的实例只有一份,
//如果保证一个线程的内部这一个实例只有一份呢
class DateUtils{
//因为这个SimpleDateFormat对象他线程不安全,解决办法就是不共享他,每一个线程都创建一个新的对象
//怎么保证每个线程里面这个 SimpleDateFormat这个对象只有一份呢 Java里面提供了ThreadLocal这个对象
private static ThreadLocal<SimpleDateFormat> td=new ThreadLocal<>();
public static String format(Date date){
//从当前线程获取SimpleDataFormat对象
SimpleDateFormat sdf=td.get();
if (sdf!=null)
return sdf.format(date);
System.out.println("创建SimpleDateFormat对象");
//当前线程没有则创建SimpleDateFormat并且存储到当前线程
sdf= new SimpleDateFormat("yyyy/MM/dd");
td.set(sdf);
/*我们使用ThreadLocal来保证创建的SimpleDateFormat只有一份
*其实是ThreadLocal关联了一个线程,这个线程内部都会有一个map,执行td.set(sdf);
* 的时候是吧sdf放到了map集合,key是ThreadLocal,值是SimpleDateFormat
* key为td(ThreadLocal),取的时候也是从当前线程取
* */
return sdf.format(date);
}
}
public class TestLock01 {
public static void main(String[] args) {
//每次执行都创建一个
DateUtils.format(new Date());
DateUtils.format(new Date());
//每个线程一份,没有共享,就没有安全问题
new Thread(){
public void run(){
DateUtils.format(new Date());
DateUtils.format(new Date());
};
}.start();
}
}
package cgb.java.thread;
import java.util.concurrent.atomic.AtomicInteger;
//JUC
public class TestThread07 {
static class Counter{
//CAS (基于硬件技术--CPU)
private AtomicInteger at=new AtomicInteger();
public int doCount(){
return at.incrementAndGet();
}
}
public static void main(String[] args) {
Counter c1=new Counter();
for(int i=0;i<100;i++){
System.out.println(c1.doCount());
}
}
}
2.4. Synchronized 关键字应用及原理分析?
public class Demo {
//排他性
//只能有一个线程进入方法内部执行
static synchronized void doMethod01(){
System.out.println("doMethod01()");
//可重入性
//这时候01和02使用的是同一把锁
doMethod02();
}
static synchronized void doMethod02(){
System.out.println("doMethod02()");
}
public static void main(String[] args) {
doMethod01();
}
}
结果:
doMethod01()
doMethod02()
Process finished with exit code 0
package cn.tedu.test;
public class TestSynchronized01 {
//排他性
//只能有一个线程进入方法内部执行
static synchronized void doMethod01(){//锁Demo.class
System.out.println("doMethod01()");
//可重入性
//这时候01和02使用的是同一把锁
doMethod02();
}
static synchronized void doMethod02(){
System.out.println("doMethod02()");
}
public synchronized void doMethod03(){//this
//这里doMethod02()跟doMethod03()不可以实现同步,他们使用的是不同的锁对象
//doMethod02()跟doMethod01()可以
//在共享数据集上进行一个互斥和协作,他们的前提条件是使用了同一把锁
System.out.println(this);
//默认使用的锁为this(当前访问这个方法对象)
}
public static void main(String[] args) {
doMethod01();
}
}
package cn.tedu.test;
public class TestSynchronized02 {
private String LOCK="CHINA";
public void doMethod01(){
synchronized (this){//this指向当前方法对应的实例对象
}//同步代码块
}
public void doMethod02(){
synchronized (LOCK){//synchronized (TestSynchronized02.this)这里我们可以任意的指定对象
// doMethod01()和doMethod02()是不同的也不可以
}
}
}
2.5. 如何理解 volatile 关键字的应用?
package cgb.java.thread;
class Looper{
//这里我们加一个volatile的话下面looper.stop();就可以停掉线程,不加的话虽然下面已经更改了值
//但是并不能停掉,这就是第一点可见性
private volatile boolean isStop;
public void loop() {
for(;;) {
if(isStop)break;
}
}
public void stop() {
isStop=true;
}
}
public class TestVolatile01 {
public static void main(String[] args) throws InterruptedException {
//Thread main=Thread.currentThread();
Looper looper=new Looper();
Thread t=new Thread(){
@Override
public void run() {
looper.loop();
}
};
t.start();
t.join(3000);
looper.stop();
}
}
package cn.tedu.test;
public class pp {
}
class Singleton{
private Singleton(){}
//这里我们要加上volatile不加的话下面可能会发生顺序颠倒问题
//分配空间,instance赋值初始化,属性初始化.(发生指令重排序)
private static volatile Singleton instance;
private static Singleton getSingleton(){//大对象,稀少用
if (instance==null){
synchronized (Singleton.class){
instance=new Singleton();//分配空间,属性初始化,instance赋值初始化
}
}
return instance;
}
}
class Counter{
private volatile int count;
public int getCount() {//read
return count; }
public synchronized void doCount() {//write
count++;
} }
2.6. 如何理解 happen-before 原则应用?
2.7. 如何理解 JAVA 中的悲观锁和乐观锁?
class Counter{
private int count;
public synchronized int count() {
count++;
return count; } }
class Counter{
private int count;
private Lock lock=new ReentrantLock();//默认是false
public int count() {
lock.lock();
try {
count++;
return count; }finally {
lock.unlock();
} } }
class Counter{
private AtomicInteger at=new AtomicInteger();//CAS
public int count() {
return at.incrementAndGet();
} }
2.8. 如何理解线程的上下文切换?
2.9.如何理解死锁以及避免死锁问题?
package cn.tedu.test;
class SyncTask01 implements Runnable {
private Object obj1;
private Object obj2;
public SyncTask01(Object o1, Object o2) {
this.obj1 = o1;
this.obj2 = o2;
}
@Override
public void run() {
synchronized (obj1) {
work();
synchronized (obj2) {
work();
}
}
}
private void work() {
try {Thread.sleep(30000);} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class TestDeadLock01 {
public static void main(String[] args) throws Exception {
Object obj1 = new Object();
Object obj2 = new Object();
Thread t1 = new Thread(new SyncTask01(obj1, obj2), "t1");
Thread t2 = new Thread(new SyncTask01(obj2, obj1), "t2");
t1.start();
t2.start();
}
}
package cn.tedu.test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
class SyncTask02 implements Runnable{
private List<Integer> from;
private List<Integer> to;
private Integer target;
public SyncTask02(List<Integer> from,List<Integer> to,Integer target) {
this.from=from;
this.to=to;
this.target=target; }
@Override
public void run() {
moveListItem(from, to, target);
}
private static void moveListItem (List<Integer> from,
List<Integer> to, Integer item) {
log("attempting lock for list", from);
synchronized (from) {
log("lock acquired for list", from);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log("attempting lock for list ", to);
synchronized (to) {
log("lock acquired for list", to);
if(from.remove(item)){
to.add(item);
}
log("moved item to list ", to);
}
}
}
private static void log (String msg, Object target) {
System.out.println(Thread.currentThread().getName() + ": " + msg + " " +
System.identityHashCode(target));
} }
public class TestDeadLock02 {
public static void main(String[] args) {
List<Integer> list1 = new ArrayList<>(Arrays.asList(2, 4, 6, 8, 10));
List<Integer> list2 = new ArrayList<>(Arrays.asList(1, 3, 7, 9, 11));
Thread thread1 = new Thread(new SyncTask02(list1, list2, 2));
Thread thread2 = new Thread(new SyncTask02(list2, list1, 9));
thread1.start();
thread2.start();
}
}
3. 线程通讯与进程通讯应用基础
3.1. 如何理解进程与线程通讯?
线程池
阻塞队列
3.2. 如何实现进程内部线程之间的通讯?
JAVA 核心基础 线程并发原理(知识点,讲解,练习,代码)
31/100
Theshy08
/**
* 有界消息队列:用于存取消息
* 1)数据结构:数组(线性结构)
* 2)具体算法:FIFO(先进先出)-First in First out
*/
public class BlockContainer<T> {//类泛型
/**用于存储数据的数组*/
private Object[] array;
/**记录有效元素个数*/
private int size;
public BlockContainer () {
this(16);//this(参数列表)表示调用本类指定参数的构造函数
}
public BlockContainer (int cap) {
array=new Object[cap];//每个元素默认值为null
} }
/**
* 生产者线程通过put方法向容器放数据
* 数据永远放在size位置
* 说明:实例方法内部的this永远指向
* 调用此方法的当前对象(当前实例)
* 注意:静态方法中没有this,this只能
* 应用在实例方法,构造方法,实例代码块中
*/
public synchronized void put(T t){//同步锁:this
//1.判定容器是否已满,满了则等待
while(size==array.length)
try{this.wait();}catch(Exception e){}
//2.放数据
array[size]=t;
//3.有效元素个数加1
size++;
//4.通知消费者取数据
this.notifyAll();
}
JAVA 核心基础 线程并发原理(知识点,讲解,练习,代码)
31/100
Theshy08
/**
* 消费者通过此方法取数据
* 位置:永远取下标为0的位置的数据
* @return
*/
@SuppressWarnings("unchecked")
public synchronized T take(){
//1.判定容器是否为空,空则等待
while(size==0)
try{this.wait();}catch(Exception e){}
//2.取数据
Object obj=array[0];
//3.移动元素
System.arraycopy(
array,//src 原数组
1, //srcPos 从哪个位置开始拷贝
array, //dest 放到哪个数组
0, //destPost 从哪个位置开始放
size-1);//拷贝几个
//4.有效元素个数减1
size--;
//5.将size位置为null
array[size]=null;
//6.通知生产者放数据
this.notifyAll();//通知具备相同锁对象正在wait线程
return (T)obj;
}
/**
* 有界消息队列:用于存取消息
* 1)数据结构:数组(线性结构)
* 2)具体算法:FIFO(先进先出)-First in First out
*/
public class BlockContainer<T> {//类泛型
/**用于存储数据的数组*/
private Object[] array;
/**记录有效元素个数*/
private int size;
public BlockContainer() {
this(16);//this(参数列表)表示调用本类指定参数的构造函数
}
public BlockContainer(int cap) {
array=new Object[cap];//每个元素默认值为null
}
//JDK1.5以后引入的可重入锁(相对于synchronized灵活性更好)
private ReentrantLock lock=new ReentrantLock(true);// true表示使用公平锁,默认是非公平锁
private Condition producerCondition=lock.newCondition();//通讯条件
private Condition consumerCondition=lock.newCondition();//通讯条件
}
/**
* 生产者线程通过put方法向容器放数据
* 数据永远放在size位置
* 说明:实例方法内部的this永远指向
* 调用此方法的当前对象(当前实例)
* 注意:静态方法中没有this,this只能
* 应用在实例方法,构造方法,实例代码块中
*/
public void put(T t){//同步锁:this
System.out.println("put");
lock.lock();
try{
//1.判定容器是否已满,满了则等待
while(size==array.length)
//等效于Object类中的wait方法
try{producerCondition.await();}catch(Exception e){e.printStackTrace();}
//2.放数据
array[size]=t;
//3.有效元素个数加1
size++;
//4.通知消费者取数据
consumerCondition.signalAll();//等效于object类中的notifyall()
}finally{
lock.unlock();
}}
/**
* 消费者通过此方法取数据
* 位置:永远取下标为0的位置的数据
* @return
*/
@SuppressWarnings("unchecked")
public T take(){
System.out.println("take");
lock.lock();
try{
//1.判定容器是否为空,空则等待
while(size==0)
try{consumerCondition.await();}catch(Exception e){}
//2.取数据
Object obj=array[0];
//3.移动元素
System.arraycopy(
array,//src 原数组
1, //srcPos 从哪个位置开始拷贝
array, //dest 放到哪个数组
0, //destPost 从哪个位置开始放
size-1);//拷贝几个
//4.有效元素个数减1
size--;
//5.将size位置为null
array[size]=null;
//6.通知生产者放数据
producerCondition.signalAll();//通知具备相同锁对象正在wait线程
return (T)obj;
}finally{
lock.unlock();
}
}
3.3. 如何实现进程之间间通讯(IPC)?
public class BioMainServer01 {
private Logger log=LoggerFactory.getLogger(BioMainServer01.class);
private ServerSocket server;
private volatile boolean isStop=false;
private int port;
public BioMainServer01(int port) {
this.port=port; }
public void doStart()throws Exception {
server=new ServerSocket(port);
while(!isStop) {
Socket socket=server.accept();
log.info("client connect");
doService(socket);
}
server.close();
}
public void doService(Socket socket) throws Exception{
InputStream in=socket.getInputStream();
byte[] buf=new byte[1024];
int len=-1;
while((len=in.read(buf))!=-1) {
String content=new String(buf,0,len);
log.info("client say {}", content);
}
in.close();
socket.close();
}
public void doStop() {
isStop=false; }
public static void main(String[] args)throws Exception {
BioMainServer01 server=new BioMainServer01(9999);
server.doStart();
} }
public class BioMainClient {
public static void main(String[] args) throws Exception{
Socket socket=new Socket();
socket.connect(new InetSocketAddress("127.0.0.1", 9999));
OutputStream out=socket.getOutputStream();
Scanner sc=new Scanner(System.in);
System.out.println("client input:");
out.write(sc.nextLine().getBytes());
out.close();
sc.close();
socket.close();
} }