java 多线程笔记
多线程
cpu只能干一件事,好像一次干很多次,是因为处理速度很快,一边一点的做 ,切换着做,就是多线程
进程和线程
-
进程process是操作系统的一个任务,有一块独立的内存
-
线程是进程的几个任务,process创建,os会自动申请主线程
并发和并行
- 并发
通过cpu调度算法,多个线程并发运行,不是同时运行 ,OS划分时间,每个进程轮流着干,用TPS或者QPS来反应这个系统的处理能力
- 并行
多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。
线程生命周期
new => runnable => run =>dead
runnable 和run之间有block
Thread 在lang包中
不可能切换一次一次
Thread1 t1 = new Thread1();
Thread2 t2 = new Thread2();
t1.start();//启动用start,进入runnale状态,一旦获得cpu时间就会运行
t2.start();
class Thread1 extends Thread {//run是干的活
public void run(){
for(int a =0;a<3;a++) {
System.out.println("hello");
}
}
}
class Thread2 extends Thread {
public void run(){
for(int a =0;a<3;a++) {
System.out.println("Hi");
}
}
}
上面写法的不足与改进(实现runnable接口,重写run)
- java单继承,继承了thread后无法继承其他类
- 执行的run和线程有耦合关系,重用性不好
Thread3 task3 = new Thread3();
Thread4 task4 = new Thread4();
Thread t3 = new Thread(task3);
Thread t4 = new Thread(task4);
t3.start();
t4.start();
class Thread3 implements Runnable {
public void run() {
for(int a =0;a<3;a++) {
System.out.println("Hi");
}
}
}
class Thread4 implements Runnable {
public void run() {
for(int a =0;a<3;a++) {
System.out.println("hallo");
}
}
}
匿名内部类写法
new Thread(new Runnable() {
public void run() {
for(int a =0;a<3;a++) {
System.out.println("I am huahuadavids");
}
}
}).start();
查看线程信息 Thread.currentThread();
返回在方法中的线程
Thread main = Thread.currentThread();
System.out.println(main);
// Thread[main,5,main] 名字优先级方法
System.out.println(main.getId());// 线程id
System.out.println(main.getName());//线程名字
线程优先级 1-10(10最高)
线程的时间片是由线程调用被动的,理论上,优先级越高,获取时间片的次数越多
p(Thread.MIN_PRIORITY);// 1
p(Thread.NORM_PRIORITY);// 5
p(Thread.MAX_PRIORITY);// 10
Thread t1 = new Thread(new Runnable() {
public void run() {
for(int i=0;i<10000;i++) {
p("t11111");
}
}
});
Thread t2 = new Thread(new Runnable() {
public void run() {
for(int i=0;i<10000;i++) {
p("t22222");
}
}
});
Thread t3 = new Thread(new Runnable() {
public void run() {
for(int i=0;i<10000;i++) {
p("t33333");
}
}
});
t1.setPriority(1);
t2.setPriority(5);
t3.setPriority(10);
t1.start();
t2.start();
t3.start();
sleep
让线程进入阻塞,超时后进入runnale状态
Thread rose = new Thread(new Runnable() {
public void run() {
for(int a=0;a<10;a++) {
System.out.println("I will jump");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("I jump !!! ");
}
});
守护线程
- 守护线程是后台线程
- 进程的前台线程都结束,后台线程也必须结束
- 当进程只剩下守护线程,所有程序线程必须over
- GC就是守护线程
Thread rose = new Thread(new Runnable() {
public void run() {
for(int a=0;a<10;a++) {
System.out.println("I will jump");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("I jump !!! ");
}
});
Thread jack = new Thread(new Runnable() {
public void run() {
while(true) {
System.out.println("You jump, I jump");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
jack.setDaemon(true);
rose.start();
jack.start();
System.out.println("main结束");
- main的线程首先结束
- rose线程结束
- jack结束
- GC是死循环
- yield方法 主动让出cpu时间,进入Runnable
join方法
join方法可以让他的线程进入block,一直到该方法的线程完成工作,一般用作多个线程的同步工作
//在匿名内部类中,使用保存匿名内部类的变量,必须是final
public static boolean isFinish = false;
public static void main(String[] args) {
final Thread download = new Thread() {
public void run() {
System.out.println("下载开始");
for(int a =0;a<101;a+=10) {
System.out.println("正在下载:"+ a+"%");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("下载结束");
isFinish = true;
}
};
Thread show = new Thread(new Runnable() {
public void run() {
try {
download.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("开始加载图片");
if(!isFinish) {
throw new RuntimeException("图片未下载完毕");
}
System.out.println("结束加载图片");
}
});
download.start();
show.start();
}
线程安全
线程的调度顺序不影响任何结果 ,cpu调用时间的不确定造成了结果的可能问题,造成了抢资源 ,java多线程导致的安全性问题,java中的锁机制是解决这个问题,就是排队,不利于效率
解决线程安全问题
加锁
-
锁能使其保护的代码以串行的形式来访问,当给一个复合操作加锁后,能使其成为原子操作。一种错误的思想是只要对写数据的方法加锁,其实这是错的,对数据进行操作的所有方法都需加锁,不管是读还是写。
-
加锁时需要考虑性能问题,不能给整个方法加锁synchronized就了事了,应该将方法中不影响共享状态且执行时间比较长的代码分离出去。
-
加锁的含义不仅仅局限于互斥,还包括可见性。为了确保所有线程都能看见最新值,读操作和写操作必须使用同样的锁对象。
不共享状态
- 无状态对象:无状态对象一定是线程安全的,因为不会影响到其他线程。
- 线程关闭: 仅在单线程环境下使用。
不可变对象
使用final修饰的对象保证线程安全,由于final修饰的引用型变量(除String外)不可变是指引用不可变,但其指向的对象是可变的,所以此类必须安全发布,也即不能对外提供可以修改final对象的接口
线程里的run方法,抛出一个异常到run之外,这个线程就被干掉了
线程安全demo
class Table {
private int beans = 20;
public int getBeans() {
if(beans == 0) {
throw new RuntimeException("no beans");
}
Thread.yield();
return beans--;
}
}
final Table table = new Table();
Thread p1 = new Thread() {
public void run() {
while(true) {
int bean = table.getBeans();
Thread.yield();
System.out.println(getName() + " " + bean);
}
}
};
Thread p2 = new Thread() {
public void run() {
while(true) {
int bean = table.getBeans();
Thread.yield();
System.out.println(getName() + " " + bean);
}
}
};
p1.start();
p2.start();
/*
Thread-0 -264733
Thread-0 -264734
Thread-0 -264735
Thread-0 -264736
Thread-0 -264737
Thread-0 -264738
Thread-0 -264739
*/
synchronized
把对象上锁,只有钥匙才能进 ,否则就在阻塞 ,就是把抢资源变成排队获取资源
class Table {
private int beans = 20;
public synchronized int getBeans() {
if(beans == 0) {
throw new RuntimeException("no beans");
}
Thread.yield();
return beans--;
}
}
同步块
缩小范围,提高效率
- 原来的
class shop {
public synchronized void buy() {
try {
Thread t = Thread.currentThread();
System.out.println(t.getName() + "进入商店");
Thread.sleep(3000);
System.out.println(t.getName() + "进入试衣间");
Thread.sleep(3000);
System.out.println(t.getName() + "付款离开");
}catch (Exception e) {
e.printStackTrace();
}
}
}
shop shop = new shop();
Thread t1 = new Thread() {
public void run() {
shop.buy();
}
};
Thread t2 = new Thread() {
public void run() {
shop.buy();
}
};
t1.start();
t2.start();
- 改进的
class shop {
public void buy() {
try {
Thread t = Thread.currentThread();
System.out.println(t.getName() + "进入商店");
Thread.sleep(3000);
synchronized(this) {
System.out.println(t.getName() + "进入试衣间");
Thread.sleep(3000);
}
System.out.println(t.getName() + "付款离开");
}catch (Exception e) {
e.printStackTrace();
}
}
}
同步和异步
线程排队就是同步,反之亦然
静态方法的同步,一定有效果
静态方法的同步,一定有效果 和对象无关
互斥锁
- synchronized叫同步锁,也叫互斥锁
- 修饰方法就是锁这个的对象
- 同步锁,2个线程不可以同一段代码
- 互斥锁, synchronized修饰的代码不同
class boo {
public synchronized void ma() {
try {
Thread t = Thread.currentThread();
String name = t.getName();
System.out.println(name + "在执行A方法");
Thread.sleep(2000);
System.out.println(name + "结束执行A方法");
}catch(Exception e) {
e.printStackTrace();
}
}
public void mb() {
try {
Thread t = Thread.currentThread();
String name = t.getName();
System.out.println(name + "在执行b方法");
Thread.sleep(2000);
System.out.println(name + "结束执行b方法");
}catch(Exception e) {
e.printStackTrace();
}
}
}
boo obj = new boo();
Thread t1 = new Thread() {
public void run() {
obj.ma();
}
};
Thread t2 = new Thread() {
public void run() {
obj.ma();
}
};
t1.start();
t2.start();
线程安全和集合
- vector是同步的
- ArrayList,linkedlist和hashSet不是同步的
- 就算是线程安全的集合,对于集合的操作和遍历,不是互斥的,需要自行维护
List<String> list = new ArrayList<String>();
list.add("one");
list.add("one1");
list.add("one");
System.out.println(list);
list = Collections.synchronizedList(list);
System.out.println(list);
Set<String> set = new HashSet<String>(list);
set = Collections.synchronizedSet(set);
Map<String,Integer> map = new HashMap<String,Integer>();
map.put("math", 100);
map = Collections.synchronizedMap(map);
线程池
- 控制线程数量
- 重用线程
- 大量使用线程和频繁创建销毁线程
固定大小的线程池
固定大小,多余的分配的线程在队列中
- showdown 把线程池中缓存的任务都干完再停
- shoudownNow 马上停
blockingQueue 双缓冲队列
解决了队列的安全问题,效率也可以