目前主要看的是 马士兵的,这里还有对应的代码:https://gitee.com/jcon/thread/tree/master/src/thread
下一篇博客的地址:https://blog.csdn.net/qq_41115379/article/details/115496657
按照Java核心技术 先知道一下
1.什么是线程
和进程区分看来,不能独享资源,但是执行cpu的最小单位
2.线程的执行
调用Thread.start方法,会创建一个执行run方法的新线程
3.基本的线程同步
就是调用synchronized 是一种内部锁,如果上了锁之后,可以把方法保护起来,调用对应的方法就需要获得内部的对象锁
先了解一些 synchronized
public class T {
/**
* 对synchronized 关键字的书写
*/
private int count=20;
public void m(){
synchronized (this){//除了 this 还可以直接 Object o=new Object() 然后用0来充当,就是有点没必要
//要注意 这里是有个中括号的 也就是所谓的包在了锁里面
//然后再里面执行一些操作
count--;
System.out.println("count = " + count);
}
}
}
从这里可以知道
synchronized是锁了一个对象,而不是一个方法或者代码
简便写法
/**
* 更简单的写法 如果这个代码是 调用了this 也就是开始就锁上了 结束才解锁的话可以进行简写
*静态的话 static 是没有new一个对象的 也就没有this 所以,要把这个修改为 synchronized(T.class)
*/
public synchronized void m1(){
count--;
System.out.println("count = " + count);
}
这里还有个实际的应用
public class T implements Runnable{
private int count=10;
@Override
public /*synchronized*/ void run() {
//实现这个run方法
count--;
//Thread.currentThread().getName() 就是调用当前thread(线程)对象的名字 process是进程
System.out.println(Thread.currentThread().getName()+"count= "+count);
}
public static void main(String[] args) {
//产生五个线程来调用
T t=new T();
for (int i=0;i<5;i++){
//新创建一个线程 然后直接启动
new Thread(t,"Thread " +i).start();
}
}
}
脏读的现象
import java.util.concurrent.TimeUnit;
/**
* 对写方法进行加锁 而不对读方法加锁
* 这样可能会产生脏读
*/
public class T {
String name;
double balance;
/**
* 因为这个方法被锁住了 但还是有可能会被非锁定方法所访问
* @param name
* @param balance
*/
//这里对写进行了上锁
public synchronized void set(String name, double balance){
this.name = name;
/* try {
//让进程sleep一下
Thread.sleep(2 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
//这一段try 是放大了 name和balance之间的时间差
this.balance = balance;
}
//对get方法也进行了上锁
//这段代码会存在问题 在this.name 和this.balance 之间可能会被getBalance所访问
//所以 如果在中间就被访问了 就有可能使得balance为默认值0 (因为还没到 this.balance=balance)
//这就是 脏读现象
public /*synchronized*/ double getBalance(String name){
return this.balance;
}
public static void main(String[] args) {
T demo08 = new T();
//新建一个线程调用写的方法
new Thread(()->demo08.set("zhangsan",100.0)).start(); //JDK1.8新特性
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 前面这个try的意思 用来等待1秒钟用的 这里的sleep也是调用了Thread的sleep
//然后调用 获取这个钱
System.out.println(demo08.getBalance("zhangsan"));
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//这里的try 也就是让时间暂停两秒 再拿zhangsan的钱
System.out.println(demo08.getBalance("zhangsan"));
}
}
可重入现象,也就是获得锁之后,可以再获得一遍
import java.util.concurrent.TimeUnit;
/**
* 对写方法进行加锁 而不对读方法加锁
* 这样可能会产生脏读
*/
public class T {
synchronized void test1(){
System.out.println("test1 start.........");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//这边在锁里面了
test2();
}
//这边也再上了一次锁
synchronized void test2(){
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test2 start.......");
}
public static void main(String[] args) {
T demo09 = new T();
demo09.test1();
}
}
线程在执行过程中,出现异常的话锁就会被释放
import java.util.concurrent.TimeUnit;
/**
* 程序在执行过程中,如果出现异常,默认情况锁会被释放
* 所以,在并发处理的过程中,有异常要多加小心,不然可能会发生不一致的情况
* 比如,在一个web app处理过程中,多个servlet线程共同访问通一个资源,这是如果异常处理不合适
* 在第一个线程中抛出异常,其他线程就会进入同步代码去,有可能访问到异常产生是的数据
* 因此要非常小心的处理同步业务逻辑中的异常
* @author Jcon
* 这里如果进行 try catch 的话 就可以实现不抛出锁 而一直执行ti操作
* 不进行 try catch的话 就会停止 从而进行t2操作
*/
public class T {
int count = 0;
synchronized void test(){
System.out.println(Thread.currentThread().getName() + " start......");
while (true) {
count ++;
System.out.println(Thread.currentThread().getName() + " count = " + count);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
/*try {*/
if (count == 5) {
int i = 1 / 0; //此处抛出异常,锁将被释放,要想不被释放,可以在这里进行catch处理,然后让循环继续
}
/* }catch (Exception e){
e.printStackTrace();
}*/
}
}
public static void main(String[] args) {
T demo11 = new T();
Runnable r = new Runnable() {
@Override
public void run() {
demo11.test();
}
};
new Thread(r, "t1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(r, "t2").start();
}
}
volatile 这个关键字让一个变量在多个线程之间可见 体现可见性
这里还有些话说,因为直接运行这个代码,发现没有体现出volatile的可见性
网上说的是:
因为计算机的速度实在是太快了,你仅用两个线程几乎不可能看到被volatile修饰的变量和不被volatile修饰的变量有什么不一样的表现。
- 保证被volatile修饰的变量对所有其他的线程的可见性
- 使用volatile修饰的变量禁止指令重排优化。
import java.util.concurrent.TimeUnit;
/**
* volatile 关键字,是一个变量在多个线程间可见
* @author Jcon
*
*/
public class T {
//啊这 可是我没有volatile也是可以直接调用成功的呀
/*volatile*/ boolean running = true;
void test(){
System.out.println("test start.......");
while (running) {
//但是出现了这个就可以 那就是说明 他之前很有可能都没有到这个running这一步 true了之后就直接变成了false
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("test end........");
}
public static void main(String[] args) {
T demo12 = new T();
//T这个类 new一个线程 然后调用test方法
new Thread(demo12 :: test, "t1").start(); //JDK1.8新特性
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//暂停两秒之后 把他设置为false 让test暂停
//原本 如果没有volatile关键字的话 直接这样设置是没有用的
demo12.running = false;
}
}
一个关于volatile和synchronize有什么区别的问题:
得背网上的答案模板,但是大致可以理解为
volatile保证可见性,而synchronize既保证可见性,又保证原子性,但是由此synchronize的效率要比volatile低不少。所以在只需要保证可见性的时候就用volatile
这也是能理解的
/**
* volatile并不能保证多个线程共同修改running变量时所带来的不一致问题,
* 也就是说volatile不能替代synchronize
* 运行下面的程序,并分析结果
* @author Jcon
*
*/
/**
* 正常来说 这个运行结果会是十万 但是确实每次都是不同的数值 主要是因为 count这个值 很有可能被其中任意一个count所取走
* volatile只保证了可见性 但是不能保证原子性 会导致写的时候冲突
*/
public class T {
//这个是可见的
volatile int count = 0;
//正常是加很多
public void test(){
for (int i = 0; i < 10000; i++) {
count ++;
}
}
public static void main(String[] args) {
T demo13 = new T();
List<Thread> threads = new ArrayList<Thread>();
//创建了10个线程
for (int i = 0; i < 10; i++) {
threads.add(new Thread(demo13::test, "thread-" + i));
}
//每一个都启动
threads.forEach((o)->o.start()); //JDK1.8新特性
//然后这里等待线程结束
threads.forEach((o)->{ //JDK1.8新特性
try {
o.join(); //等线程执行完毕之后才执行主线程main
} catch (Exception e) {
e.printStackTrace();
}
});
//打印count值
System.out.println(demo13.count);
}
}
原子类AtomXXX类:
同时可以证明 AtomXXX类的多个方法不构成原子性
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 解决同样的问题的更高效的方法,使用AtomXXX类
* AtomXXX类本身方法都是原子性的,但不能保证多个方法连续调用是原子性
* @author Jcon
*
*/
public class T {
//int count = 0;
//就是原子性操作的Integer类型 可以保证不能让多个线程或者多个方法同时连续调用
AtomicInteger count = new AtomicInteger(0);
public /*synchronized*/ void test(){
for (int i = 0; i < 10000; i++) {
//count ++;
//效率比synchronize高很多,是系统底层的 相当于就是count++
//这里有个很细节的地方 原子性只体现在 方法的调用内部 而不能在两个方法之间 在两个方法之间 依然会存在线程的竟然与
//比如我再对count进行别的操作 这样线程就会冲入 也就是不能保证多个方法连续调用是原子性
/* if(count.get()<1000) {*/
count.incrementAndGet(); //count++
/* }*/
}
}
//主函数还是同样的操作
public static void main(String[] args) {
T demo15 = new T();
List<Thread> threads = new ArrayList<Thread>();
for (int i = 0; i < 10; i++) {
threads.add(new Thread(demo15::test, "thread-" + i));
}
threads.forEach((o)->o.start()); //JDK1.8新特性
threads.forEach((o)->{ //JDK1.8新特性
try {
o.join(); //等线程执行完毕之后才执行主线程main
} catch (Exception e) {
e.printStackTrace();
}
});
System.out.println(demo15.count);
}
}
原子类AtomXXX类体现可见性:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
/**
* volatile 关键字,是一个变量在多个线程间可见
* @author Jcon
*
*/
public class T {
//啊这 可是我没有volatile也是可以直接调用成功的呀
/* volatile*/ AtomicBoolean running =new AtomicBoolean(true);
void test(){
System.out.println("test start.......");
while (running.get()) {
//但是出现了这个就可以 那就是说明 他之前很有可能都没有到这个running这一步 true了之后就直接变成了false
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("test end........");
}
public static void main(String[] args) {
T demo12 = new T();
//T这个类 new一个线程 然后调用test方法
new Thread(demo12 :: test, "t1").start(); //JDK1.8新特性
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//暂停两秒之后 把他设置为false 让test暂停
//原本 如果没有volatile关键字的话 直接这样设置是没有用的
demo12.running.set(false);
}
}