文章目录
刚入职场的小菜鸟,深知知识需要积累,需要记录,博文主要目的就是方便自己复习和记录遇到的问题及解决办法。
对于知识点写作思路就是:基础概念(解决是什么,是为了解决什么),例子(怎么用),细节(具体使用过程中碰到的问题以及与其他的联系)
声明:基本都是自己概括而成,深知有些话要究竟,无底洞,很耗时,针对我的情况我会间而化之!
线程
基础概念
进程: 操作系统中运行的程序就是进程,多个就是多进程。
线程: 进程中可以划分成多个并行执行的过程,一个就是线程,多个就是多线程。
常识:
- 一个计算机中,只有一个cpu,同一个时刻,只能处理一个运算,但由于计算机的运行速度极快,会不停的切换处理的任务,看起来就行同时执行一样!
- 我们平时写的程序就是在jvm中执行,jvm也是进程,在其中开辟多个线程,执行并发的任务, 这就是java的多线程技术。
类:
万物皆对象,在java中也是用对象来代表底层的物理线程 - Thread类
启动线程的两种方式:
a. 启动线程的方式1:
i. 创建一个类 实现Runnable接口 在其中的run()方法中编写启动的线程要执行的代码
ii. 创建改类的对象,传入Thread的构造方法 ,创建Thread对象
iii. 调用Thread对象的start()方法,启动线程
b. 启动线程的方式2:
i. 写一个类继承Thread,覆盖其中的run()方法,在其中编写启动的线程要执行的代码
ii. 创建该Thread类的子类的对象
iii. 调用Thread类的子类的对象的start()方法,启动线程
两种线程启动方式的比较:
- java是单继承的,继承的方式创建线程,将会占用了extends关键字,这在类本身需要继承其他类的情况下无法使用,影响扩展性。
- java是多实现的,实现接口的数量没有限制,所以实现接口创建线程的方式并不会受到单继承的限制
线程并发的细节:
a. 主线程和其他线程比起来,唯一特殊的地方是它是程序的入口,除此之外没有任何高低 先后 差别。
b. 多个线程并发的过程中,线程在不停的无序的争夺cpu,某一时刻哪个线程抢夺到,哪个线程就执行,由于cpu运行速度非常快,看起来似乎这些线程都在并发的执行。
c. 线程是在进程内部执行的,进程内部只要有任意一个非守护线程存活,进程就不会结束。
关闭线程:
官方的建议是,应该由程序本身提供相应的开关,来控制线程的运行和停止。通常由一个静态的布尔类型来作为这种开关。
Thread中的其他常用方法:
线程优先级相关的字段,本质上是int类型的常量值,取值范围为1 - 10 ,值越大优先级越高
多线程并发安全问题:
- 多个线程并发修改共享变量
多线程并发安全问题产生的条件:
a. 有共享资源
b. 有多线程并发操作了共享资源
c. 有多线程并发操作了共享资源 且涉及到了修改操作
解决多线程并发安全问题:
解决多线程并发安全问题的关键,就是破坏产生多线程并发安全问题的条件
禁止共享资源 – ThreadLocal
禁止多线程并发操作 – Syncronized
禁止修改 – ReadWriteLock
Syncronized同步代码块:
同步代码块的结构:
Syncronized(锁对象){
要同步的代码
}
锁对象的选择的原则:
任意对象都可以作为锁对象使用
但要保证,所有的并发线程都应该能够访问到该所对象,且访问的必须是同一个锁对象
常用的锁对象:
自己创建一个专用的对象
使用共享资源作为锁对象
使用类的字节码对象作为锁对象
例子:
创建线程 - Runnable
package cn.tedu.thread;
/**
* 创建线程的方式一:实现Runnable接口的方式
*/
//--步骤1:写一个类实现Runnable接口
class MyRunnable01 implements Runnable{
@Override
public void run() {
while(true){
System.out.println("看电影。。。");
}
}
}
public class ThreadDemo01 {
public static void main(String[] args) {
//--步骤2:创建Thread对象,并传入Runnable接口实现类的对象
Thread t1 = new Thread(new MyRunnable01());
//--步骤3:调用Thread的start()方法 真正启动底层线程 运行指定线程代码
t1.start();
//--主线程并发执行其他任务
while(true){
System.out.println("写代码。。。");
}
}
}
创建线程 - Thread
package cn.tedu.thread;
/**
* 创建线程的方式一:继承Thread方式
*/
//--步骤1:写一个类继承Thread
class MyThread extends Thread{
@Override
public void run() {
while(true){
System.out.println("看电影。。。");
}
}
}
public class ThreadDemo02 {
public static void main(String[] args) {
//--步骤2:创建MyThread类的对象
MyThread t1 = new MyThread();
//--步骤3:调用t1的start()方法 启动线程
t1.start();
//--主线程并发执行其他任务
while(true){
System.out.println("写代码。。。");
}
}
}
线程类中的常用方法
package cn.tedu.thread;
/**
* 线程类中的常用方法
*/
public class ThreadDemo04 {
public static void main(String[] args) throws Exception {
// //--getId() getName() setName()
// Thread t1 = new Thread(new Runnable(){
// @Override
// public void run() {
//
// }
// });
// System.out.println(t1.getId());
// System.out.println(t1.getName());
// t1.setName("PQThread-01");
// System.out.println(t1.getName());
//
// //--setPriority(int newPriority) getPriority()
// Thread t1 = new Thread(new Runnable() {
// private int i = 0;
// @Override
// public void run() {
// while(true)
// System.out.println(Thread.currentThread().getId()+":"+(++i));
// }
// });
// Thread t2 = new Thread(new Runnable() {
// private int i = 0;
// @Override
// public void run() {
// while(true)
// System.out.println(Thread.currentThread().getId()+":"+(++i));
// }
// });
// t1.setPriority(10);
// t2.setPriority(1);
// System.out.println(t1.getPriority());
// System.out.println(t2.getPriority());
// t1.start();
// t2.start();
//--sleep
System.out.println("aaaa");
Thread.sleep(3000);
System.out.println("bbbb");
}
}
多线程并发安全问题
package cn.tedu.thread;
/**
* 多线程并发安全问题
*/
public class ThreadDemo05 {
public static String name = "马冬梅";
public static String gender = "女";
public static void main(String[] args) {
new Thread(new PrintThread()).start();
new Thread(new ChangeThread()).start();
}
}
class ChangeThread implements Runnable{
@Override
public void run() {
while(true){
synchronized (ThreadDemo05.class) {
if("马冬梅".equals(ThreadDemo05.name)){
ThreadDemo05.name = "夏洛";
ThreadDemo05.gender = "男";
}else{
ThreadDemo05.name = "马冬梅";
ThreadDemo05.gender = "女";
}
}
}
}
}
class PrintThread implements Runnable{
@Override
public void run() {
while(true){
synchronized (ThreadDemo05.class) {
System.out.println("姓名:" + ThreadDemo05.name + ",性别:" + ThreadDemo05.gender);
}
}
}
}
package cn.tedu.thread;
public class ThreadDemo06 {
public static int tickets = 10;
public static void main(String[] args) {
new Thread(new Saler()).start();
new Thread(new Saler()).start();
}
}
class Saler implements Runnable{
@Override
public void run() {
while(ThreadDemo06.tickets>0){
synchronized (ThreadDemo06.class) {
ThreadDemo06.tickets--;
System.out.println(Thread.currentThread().getId() + "窗口,卖出去了一张票。。剩余票数为" + ThreadDemo06.tickets);
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
死锁问题
概念:
- 死锁是一种并发锁定的特殊状态,指的是,当具有多个共享资源时 一部分线程持有一部分资源的锁 要求另外的线程持有的另外的资源的锁 形成了各自持有各自的锁而要求对方的锁的状态 这样 进入了一个互相等待的状态 都无法继续执行 则称之为产生了死锁
- 死锁并不是一种真正的锁,而是一种特殊状态,会造成程序无法继续运行或退出,所以要尽力的解决死锁
死锁产生的条件
a. 多把锁
b. 多个线程
c. 同步嵌套
在Synchronized代码块中再包含Synchronized代码块,这就意味着,占用着一部分锁,再要求另一部分锁
目录,再往上一拉
解决死锁
- 避免死锁
避免同步嵌套来避免死锁的产生 - 检测并打断死锁
有时无法进行避免死锁的操作,此时只能不停的检测是否有死锁产生,如果有死锁产生,则打断死锁,所谓的打断死锁,就是将造成死锁的某一线程错误退出,打断对锁互相要求的环,从而使程序可以正常运行下去。
线程之间的通信
-
在程序运行过程中,线程是相对独立的单位,多个线程之间并行的执行,并不会有太多的沟通,每个线程都有属于自己的内存空间,且无法互相访问,所以可以认为多个线程之间是隔离的状态,并没有过多的信息传递。
-
而线程在并发运行的过程中,还会无序抢夺cpu,造成执行的顺序不确定,使执行的结果变得不可预期
-
有时我们希望能够实现 多个线程之间进行信息的传递 或 执行过程的协调 ,这样的技术称之为线程间的通信技术
线程间的通信技术:
- 共享内存机制 - 多个线程之间进行信息的传递
- 等待唤醒机制 - 多个线程执行过程的协调
例子
1. 线程间的通信机制 - 共享内存机制 - 多个线程之间的信息传递
每个线程都各自有各自的内存空间,且无法互相访问,当多个线程需要进行信息的共享时,可以在多个线程都可以看到的公共的内存中保存数据,从而实现两者的通信
案例:某一个线程通过控制一个布尔类型的信号量 控制另一个线程执行的流程
package cn.tedu.thread;
/**
* 线程间的通信 - 共享内存方式传递信息
*/
public class Demo03 {
public static boolean canRun = true;
public static void main(String[] args) {
new Thread(new Thread03_Master()).start();
new Thread(new Thread03_Slave()).start();
}
}
class Thread03_Master implements Runnable{
@Override
public void run() {
try {
Thread.sleep(2000);
Demo03.canRun = false;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Thread03_Slave implements Runnable{
@Override
public void run() {
while(Demo03.canRun){
System.out.println("小弟在运行。。。");
}
System.out.println("小弟告辞了。。。");
}
}
2. 线程之间的通信技术 - 等待唤醒机制 - 协调线程执行的先后顺序
- 多个线程在并发的过程中,互相抢夺cpu,造成执行的先后顺序是不确定的,如果需要控制线程的执行先后顺序,可以采用等待唤醒的机制
- 在锁对象身上,具有等待、唤醒的方法,这些方法其实是定义在Object类上的,所以任意对象作为锁对象时,都可以有等待 唤醒的方法使用
目录,再往上一拉
案例:利用等待唤醒机制 实现一个阻塞式队列
package cn.tedu.thread;
import java.util.LinkedList;
/**
* 等待唤醒机制 案例 - 实现阻塞式队列
* @author Administrator
*
*/
class MyBlockingQueue{
private LinkedList queue = new LinkedList<>();
private int MaxSize = 0;
public MyBlockingQueue(int size) {
this.MaxSize = size;
}
//--如果满了还要加 要阻塞当前操作线程
public synchronized void add(Object obj){
try {
if(queue.size() >= this.MaxSize){
//--不能往里加,阻塞当前线程,直到有人取走,队列变为非满
this.wait();
}
queue.add(obj);
this.notify();
} catch (InterruptedException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
//--如果空了还要取 要阻塞当前操作线程
public synchronized Object take(){
try {
if(queue.size() <=0 ){
this.wait();
}
Object obj = queue.poll();
this.notify();
return obj;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
public class Demo04 {
public static void main(String[] args) {
MyBlockingQueue bq = new MyBlockingQueue(3);
new Thread(new Runnable() {
@Override
public void run() {
bq.add("aaa");
bq.add("bbb");
bq.add("ccc");
bq.add("ddd");
bq.add("eee");
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(bq.take());
System.out.println(bq.take());
System.out.println(bq.take());
System.out.println(bq.take());
System.out.println(bq.take());
}
}).start();
}
}
线程之间的状态转换
多线程-其他方法
setDaemon()
- 线程从是否可以独立执行的角度,可以分为用户线程和守护线程
- 通常创建出来的线程都是用户线程,但可以通过setDaemon(true)将用户线程转换为守护线程用户线程可以独立运行 守护线程不可以 也就是说 如果进程中 已经没有用户线程 只剩下守护线程 则守护线程会自动退出
目录,再往上一拉
案例:王者荣耀
package cn.tedu.thread;
/**
* 守护线程 - 王者荣耀 游戏开发
*/
public class Demo05 {
public static void main(String[] args) throws Exception {
new Thread(new ShuiJing()).start();
Thread t1 = new Thread(new Hero("安其拉"));
Thread t2 = new Thread(new Hero("孙悟空"));
Thread t3 = new Thread(new Hero("诸葛亮"));
Thread t4 = new Thread(new Hero("吕布"));
Thread t5 = new Thread(new Hero("亚瑟"));
t1.setDaemon(true);
t2.setDaemon(true);
t3.setDaemon(true);
t4.setDaemon(true);
t5.setDaemon(true);
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
while(ShuiJing.blood>0){
Thread.sleep(1000);
ShuiJing.blood -= 8;
}
}
}
class Hero implements Runnable{
private String name = null;
public Hero(String name) {
this.name = name;
}
@Override
public void run() {
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"说:为了水晶,冲啊~~~碾碎他们。。");
}
}
}
class ShuiJing implements Runnable{
public static int blood = 100;
@Override
public void run() {
while(this.blood>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("全军出击,守卫水晶。。血量剩余"+blood);
}
System.out.println("水晶被打爆了。。游戏结束。。。");
}
}
join()
- 在当前线程执行时可以调用另一个线程的join方法 ,则当前线程立即进入阻塞模式,直到被join进来的线程执行完成为止
package cn.tedu.thread;
/**
* join
* @author Administrator
*
*/
public class Demo06 {
public static void main(String[] args) throws Exception {
System.out.println("上数学课。。。");
System.out.println("上数学课。。。");
System.out.println("上数学课。。。");
System.out.println("上数学课。。。");
Thread t = new Thread(new Thread_06());
t.start();
t.join();//将进入阻塞模式 直到 t执行完成
System.out.println("上数学课。。。");
System.out.println("上数学课。。。");
System.out.println("上数学课。。。");
System.out.println("上数学课。。。");
}
}
class Thread_06 implements Runnable{
@Override
public void run() {
System.out.println("做广播体操开始。。。");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("做广播体操结束。。。");
}
}