一、线程安全问题
(一)问题描述
1、原因:某段代码在操作一个数据的时候,还没操作完,
CPU
就将资源切换到其它线程中去了,就会导致数据的不完整或者错误
2、解决:要么不操作,要么一次性操作完
(二)同步代码块
1、概述:使用一种格式,让某段代码执行的时候,
CPU
不会切换到影响这段代码的代码上,这样基本保证其它程序的执行,也不会影响自己代码的执行
2、格式
synchronized(同步锁对象) {
需要保证执行完整性的代码;
}
3、效果
(1)当
CPU
想要去执行同步代码块的时候,需要先获取同步锁对象,获取之后就能执行同步代码块里面的内容,当CPU
想要执行其它线程的时候,但是不会切换到影响具有相同同步锁对象的代码上
(2)当
CPU
执行完了当前代码块中的内容时,会释放同步锁对象,
CPU
就可以运行到其它线程中去,执行其它代码
public class Demo02_Synchronized {
public static void main(String[] args) {
PrintString ps = new PrintString();
new Thread() {
@Override
public void run() {
while (true) {
ps.test01();
}
}
}.start();
new Thread() {
@Override
public void run() {
while (true) {
ps.test02();
}
}
}.start();
}
}
class PrintString {
Object obj = new Object();
public void test01() {
synchronized (obj) {
System.out.print("华");
System.out.print("清");
System.out.print("远");
System.out.println("见");
}
}
public void test02() {
synchronized (obj) {
System.out.print("J");
System.out.print("A");
System.out.print("V");
System.out.println("A");
}
}
}
二、线程同步
(一)同步方法
1、同步代码块:在某段代码执行的时候,希望
CPU
不要切换到影响这段代码的线程上去
2、如果某个方法中,所有的代码都要加上同步代码块,就可以使用同步方法让代码格式变得更简单
3、格式
修饰符 【static】 synchronized 返回值类型 方法名(参数列表) {
需要保证同步的代码块;
}
4、同步锁对象
(1)非静态方法:同步锁对象是
this
,即当前调用者,谁来调用这个方法,同步锁对象就是谁
(2)静态方法:同步方法所在类的字节码文件【类名
.class
】,哪个类调用这个同步方法,同步方法得到锁对象就会说那个类的字节码对象
public class Demo02 {
public static void main(String[] args) {
PrintStr ps = new PrintStr();
new Thread(){
@Override
public void run() {
while (true){
ps.test4();
}
}
}.start();
new Thread(){
@Override
public void run() {
while (true){
ps.test5();
}
}
}.start();
}
}
class PrintStr{
//静态同步代码块
// public static void test(){
// synchronized (PrintStr.class) {
// System.out.print("A");
// System.out.print("B");
// System.out.print("C");
// System.out.println("D");
// }
// }
// public static void test1(){
// synchronized (PrintStr.class){
// System.out.print("华");
// System.out.print("清");
// System.out.print("远");
// System.out.println("见");
// }
// }
//静态同步方法
// public static synchronized void test2(){
// System.out.print("华");
// System.out.print("清");
// System.out.print("远");
// System.out.println("见");
// }
// public static synchronized void test3(){
// System.out.print("A");
// System.out.print("B");
// System.out.print("C");
// System.out.println("D");
// }
//非静态同步方法
public synchronized void test4(){
synchronized (this){
System.out.print("华");
System.out.print("清");
System.out.print("远");
System.out.println("见");
}
}
public synchronized void test5(){
synchronized (this){
System.out.print("A");
System.out.print("B");
System.out.print("C");
System.out.println("D");
}
}
}
(二)死锁
1、
A
线程具有甲资源,继续执行需要乙资源,
B
线程具有乙资源,继续执行需要甲资源;两条线程同时拥有对象需要的资源,两条线程都不肯释放自己拥有的资源,所以谁也不能继续执行,就形成了死锁。
2、代码表现:如果有了同步代码块的嵌套,就可能出现死锁。避免使用同步代码块的嵌套
public class Demo04_Dead {
public static void main(String[] args) {
Thread t1 = new Thread("家") {
@Override
public void run() {
synchronized ("车钥匙") {
System.out.println("车钥匙在家里");
synchronized ("家钥匙") {
System.out.println("进去家拿到车钥匙,既能进去家,也能进去车");
}
}
}
};
Thread t2 = new Thread("车") {
@Override
public void run() {
synchronized ("家钥匙") {
System.out.println("家钥匙在车里");
synchronized ("车钥匙") {
System.out.println("进去车拿到家钥匙,既能进去车,也能进去家");
}
}
}
};
t1.start();
t2.start();
}
}
(三)火车票案例
三个窗口,同时售卖
100
张票
打印某个窗口卖出了一张,还剩多少张
public class Demo03 {
public static void main(String[] args) {
//继承
// Windows w1 = new Windows("A窗口");
// Windows w2 = new Windows("B窗口");
// Windows w3 = new Windows("C窗口");
// w1.start();
// w2.start();
// w3.start();
//实现
MyWindows mw = new MyWindows();
Thread t1 = new Thread(mw,"A窗口");
Thread t2 = new Thread(mw,"B窗口");
Thread t3 = new Thread(mw,"C窗口");
t1.start();
t2.start();
t3.start();
}
}
//实现方法
class MyWindows implements Runnable{
private static int tickets = 100;
@Override
public void run() {
while (true){
synchronized (MyWindows.class){
if (tickets == 0){
break;
}
tickets--;
System.out.println(Thread.currentThread().getName() + "卖了1张票,还剩" + tickets + "张");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
//继承方式
class Windows extends Thread {
private static int tickets = 100;
@Override
public void run() {
while (true) {
synchronized (Windows.class) {
if (tickets == 0) {
break;
}
tickets--;
System.out.println(getName() + "卖了1张票,还剩" + tickets + "张");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public Windows() {
}
public Windows(Runnable target) {
super(target);
}
public Windows(String name) {
super(name);
}
public Windows(Runnable target, String name) {
super(target, name);
}
}
三、线程中其它方法
(一)其它方法
1、
join
()
:是当前线程挂起,等待加入的线程执行完毕之后,再恢复执行
2、
yield
()
:线程让步。和
sleep
方法很像,只是短暂的挂起当前线程,让别的线程先运行,而自己进入就绪态,线程只是让步于优先级相同的或者更高的线程
public class Demo04 {
public static void main(String[] args) {
//test();
//线程让步
Thread t3 = new Thread("1111"){
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(i+getName());
}
}
};
Thread t4 = new Thread("222222222222222"){
@Override
public void run() {
Thread.yield();
for (int i = 0; i < 1000; i++) {
System.out.println(i+getName());
}
}
};
t3.start();
t4.start();
}
private static void test() {
//线程挂起
Thread t1 = new Thread("111"){
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(i+getName());
}
}
};
Thread t2 = new Thread("22222222222222"){
@Override
public void run() {
try {
join(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 1000; i++) {
System.out.println(i+getName());
}
}
};
t1.start();
t2.start();
}
}
(二)线程协作
1、案例:让两条线程交替打印
1-100
之间的数字
2、所用方法
(1)
wait
()
:让当前线程进入无限期的等待,并释放同步锁对象
(2)
notify
()
:唤醒一个正在
wait
的线程,如果有多个线程需要被唤醒,唤醒优先级最高的拿一个
(3)
notifyAll
()
:唤醒所有被
wait
的线程
3、注意
(1)
wait()
、
notify()
、
notifyAll()
:必须使用同步代码块或者同步方法的同步锁对象来调用
(2)
wait()
、
notify()
、
notifyAll()
:必须在同步代码块或者同步方法中使用
(3)
wait()
、
notify()
、
notifyAll()
:属于
Object
类型,因为任何一个类型的实例都有可能成为同步锁对象,所以将这些方法放入Object
中,任意类型都能调用
public class Demo05 {
public static void main(String[] args) {
MyClass mc = new MyClass();
Thread t1 = new Thread(mc,"线程1111");
Thread t2 = new Thread(mc,"线程2222222222");
}
}
class MyClass implements Runnable{
private int num = 1;
@Override
public void run() {
while (true){
synchronized (this){
this.notify();
if (num>100){
break;
}
System.out.println(num+Thread.currentThread().getName());
num++;
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
(三)sleep和wait区别
1、相同点:都是让线程进入到阻塞态
2、不同点
(1)方法所属不同:
sleep
属于
Thread
类中,
wait
属于
Object
类中
(2)调用要求不同:
sleep
方法在任何场景下都可以使用,
wait
方法必须在同步方法或者同步代码块中使用
(3)关于释放同步锁对象:
sleep
方法让线程进入休眠状态但是不会释放同步锁对象;
wait
方法让线程进入无限期的等待并释放同步锁对象
四、线程的生命周期
(一)概述
1、线程是一个动态的概念,有创建的时候,也有运行的时候,也有死亡的时候,从生到死的过程被称之为生命周期
2、状态:
(1)新建态:线程对象创建出来之后,没有
start
(2)就绪态:线程
start
方法,但是
CPU
没来临
(3)运行态:正在运行的线程处于这种状态
(4)阻塞态:线程主动休息、或者缺少一部分执行必要的资源,即使
CPU
来临了也不执行
(5)死亡态:线程完成了业务逻辑,或者出现了异常打断执行,或者线程被破坏
(二)生命周期图
(三)Java中关于线程状态的描述
1、线程状态指示理论上的描述,
Java
中给出了更加精确的描述,可以有各个不同的状态
2、获取线程状态的方法:
getState
()
3、具体描述
(1)
NEW
:尚未
start
的线程
(2)
RUNNABLE
:正在运行的线程
(3)
BLOCKED
:等待锁对象
(4)
WAITING
:被
wait
的线程处于这种状态
(5)
TIMED_WAITING
:有时限的等待:例如
sleep
(6)
TERMINATED
:死亡态线程
五、线程池
(一)概述
1、不需要手动创建线程,线程池会自动创建线程
2、在业务逻辑破坏力大的情况下,线程被破坏的十几号,线程池会安排其它线程完整其余业务逻辑
3、当业务逻辑非常简单的时候,只需要将任务提交给线程池,线程池会安排线程执行任务,任务执行完成之后,线程不会被当做垃圾清理,会继续活跃在线程池中,等待下一次被调用
(二)使用
1、步骤
(1)获取线程池对象
(2)创建任务对象
(3)将任务提交给线程池
2、获取线程池对象
(1)工具类:
Executors
(2)
newFixedThreadPool
(int nThreads)
:创建一个线程池,线程数量由参数列表决定
(3)
newSingleThreadExecutor
()
:创建单线程的线程池
3、将任务提交给线程池
(1)
submit
(Runnable task)
:将任务提交线程池
(2)
shutdown
()
:结束线程池,执行已经提交的任务,不接受新任务
(3)
shutdownNow
()
:结束线程池。试图结束正在执行的任务。不执行还未提交的任务
六、快捷键
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
public class Demo11 {
//主方法的生成 main psvm
public static void main(String[] args) throws ParseException {
//输出语句sout
System.out.println();
//创建对象写后面部分自动生成前面 ctrl + alt + V
ArrayList<Object> list = new ArrayList<>();
//选定代码快速生成方法 ctrl + alt + M
//for循环 fori foreach
int[] arr = null;
for (int ele : arr) {
}
//代码上移 alt + shift + ↑
//复制一行代码 ctrl + D
//光标在一行代码中间,但是需要直接进入下一行书写代码:shift + 回车
//代码格式调整:ctrl + alt + L
//异常处理 接口方法重写 抽象类方法重写 移动光标在标错代码出:alt + 回车
new SimpleDateFormat("").parse("");
}
}