1. 同步产生的原因
线程在运行过程中出现了安全问题
出现安全问题有这样三种可能性
A:是否是多线程环境
B:是否有共享数据
C:是否有多条语句操作共享数据
对于A和B两种可能性,是不能够强迫程序改变的,所以我们要想办法在C这种可能性上解决。
2. 同步的形式
2.1 同步代码块。
格式如下:
synchronized(对象) //任意对象都可以
{
需要被同步的代码
}
注意:
a:这里的对象就是同步锁。
b:需要同步的代码是:把多条语句操作共享数据的代码的部分给包起来
c:同步是在有两个线程以上的情况下使用并且多个线程必须使用同一把锁
同步利弊:
同步的好处
同步的出现解决了多线程的安全问题。
同步的弊端
当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
示例:同步代码块实现同步
这是一个卖电影票的案例,三个售票台卖100张票
SellTicket.java
public class SellTicket implements Runnable {
private int number = 100;
private Object obj = new Object(); //申明一个锁对象,注意,必须是同一把锁
@Override
public void run() {
while (true) {
synchronized (obj) {
if (number > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "在出售第" + (number--) + "张票");
}
}
}
}
}
SellTicketDemo.java
public class SellTicketDemo {
public static void main(String[] args) {
// 创建三个线程对象
SellTicket st = new SellTicket();
Thread st1 = new Thread(st, "窗口1");
Thread st2 = new Thread(st, "窗口2");
Thread st3 = new Thread(st, "窗口3");
st1.start();
st2.start();
st3.start();
}
}
2.2 同步函数
•就是把同步关键字加到函数上,让函数具备同步性
public class SellTicket implements Runnable {
private int number = 100;
@Override
public void run() {
synchronized (this) {
sellTicket();
}
}
private synchronized void sellTicket() {
while (true) {
if (number > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第"
+ number-- + "张票");
}
}
}
}
注意:在调用方法的时候也是需要加锁的
同步函数的锁就是this,代表的是本方法的对象
2.3当同步函数被static修饰时
public class SellTicket implements Runnable {
private static int number = 100;
@Override
public void run() {
synchronized (SellTicket.this) {
sellTicket();
}
}
private static synchronized void sellTicket() {
while (true) {
if (number > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第"
+ number-- + "张票");
}
}
}
}
同步锁对象是类的字节码文件对象,类名.class
静态函数在加载时所属于类,这时有可能还没有该类产生的对象,但是该类的字节码文件加载进内存就已经被封装成了对象,
这个对象就是该类的字节码文件对象。所以静态加载时,只有一个对象存在,那么静态同步函数就使用的这个对象。这个对象就是 类名.class
3. 线程的同步锁
这个对象就是该类的字节码文件对象。所以静态加载时,只有一个对象存在,那么静态同步函数就使用的这个对象。这个对象就是 类名.class
3. 线程的同步锁
虽然我们可以理解同步代码块和同步方法的锁对象问题,
但是我们并没有直接看到在哪里加上了锁,
在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
但是我们并没有直接看到在哪里加上了锁,
在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
3.1 Lock中的两个方法
void lock():获取锁
void unlock():释放锁
void unlock():释放锁
Lock是一个接口,其实现类有ReentrantLock
锁的应用
public class SellTicket implements Runnable {
// 定义票
private int tickets = 100;
// 定义锁对象
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
try {
// 加锁
lock.lock();
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (tickets--) + "张票");
}
} finally {
// 释放锁
lock.unlock();
}
}
}
}
3.2 同步死锁问题概述
是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象
同步示例:
public class MyLock {
// 创建两把锁对象
public static final Object objA = new Object();
public static final Object objB = new Object();
}
public class DieLock extends Thread {
private boolean flag;
public DieLock(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag) {
synchronized (MyLock.objA) {
System.out.println("if objA");
synchronized (MyLock.objB) {
System.out.println("if objB");
}
}
} else {
synchronized (MyLock.objB) {
System.out.println("else objB");
synchronized (MyLock.objA) {
System.out.println("else objA");
}
}
}
}
}
同步中嵌套同步就容易产生死锁,同步函数中有同步代码块,同步代码块中还有同步函数
4. 线程间的通信
针对同一个资源的操作有不同种类的线程
4.1 采用同步的方式
GetThread.java
public class GetThread implements Runnable {
Student student = new Student();
public GetThread(Student s){
this.student = s;
}
@Override
public void run() {
while (true) {
synchronized (student) {
System.out.println(student.name + "-----" + student.age);
}
}
}
}
SetThread.java
public class SetThread implements Runnable {
Student student = new Student();
int x = 0;
public SetThread(Student s) {
this.student = s;
}
@Override
public void run() {
while (true) {
synchronized (student) {
if (x % 2 == 0) {
student.name = "xx";
student.age = 18;
} else {
student.name = "yy";
student.age = 20;
}
x++;
}
}
}
}
4.2 采用等待唤醒机制
等待唤醒机制思路分析
Object类中提供了三个方法:
wait():等待
notify():唤醒单个线程
notifyAll():唤醒所有线程
为什么这些方法不定义在Thread类中呢?
这些方法的调用必须通过锁对象调用,而我们刚才使用的锁对象是任意锁对象。
所以,这些方法必须定义在Object类中。
GetThread.java
wait():等待
notify():唤醒单个线程
notifyAll():唤醒所有线程
为什么这些方法不定义在Thread类中呢?
这些方法的调用必须通过锁对象调用,而我们刚才使用的锁对象是任意锁对象。
所以,这些方法必须定义在Object类中。
GetThread.java
package cn.itcast_05;
public class GetThread implements Runnable {
private Student s;
public GetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
synchronized (s) {
if(!s.flag){
try {
s.wait(); //t2就等待了。立即释放锁。将来醒过来的时候,是从这里醒过来的时候
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(s.name + "---" + s.age);
//修改标记
s.flag = false;
//唤醒线程
s.notify(); //唤醒t1
}
}
}
}
SetThread.java
package cn.itcast_05;
public class SetThread implements Runnable {
private Student s;
private int x = 0;
public SetThread(Student s) {
this.s = s;
}
@Override
public void run() {
while (true) {
synchronized (s) {
//判断有没有
if(s.flag){
try {
s.wait(); //t1等着,释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (x % 2 == 0) {
s.name = "xx";
s.age = 27;
} else {
s.name = "yy";
s.age = 30;
}
x++; //x=1
//修改标记
s.flag = true;
//唤醒线程
s.notify(); //唤醒t2,唤醒并不表示你立马可以执行,必须还得抢CPU的执行权。
}
//t1有,或者t2有
}
}
}
等待唤醒机制方法的改进 一般我们将对这个类的操作放在这个类的java文件中,所以可以这样设置等待唤醒机制
public class Student {
private String name;
private int age;
private boolean flag; // 默认情况是没有数据,如果是true,说明有数据
public synchronized void set(String name, int age) {
// 如果有数据,就等待
if (this.flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 设置数据
this.name = name;
this.age = age;
// 修改标记
this.flag = true;
this.notify();
}
public synchronized void get() {
// 如果没有数据,就等待
if (!this.flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 获取数据
System.out.println(this.name + "---" + this.age);
// 修改标记
this.flag = false;
this.notify();
}
}
Java中使用ThreadGroup来表示线程组,
它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。
线程组里面的常见方法
它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。
线程组里面的常见方法
public ThreadGroup (String name):构造一个新的线程组
通过API查看,发现可以将线程组的所有线程设为后台线程、设置线程组的最高优先级、可以中断线程组的所有线程等等。
Thread里面的方法可以指定线程属于哪个线程组
Thread(ThreadGroup group, Runnable target, String name)