Java多线程之线程同步

1.进程与线程
1.1 进程的概念
是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,是系统进行资源分配和调用的独立单元 。只有运行的程序才会出现进程。一个进程中可以有一个或者多个线程。

多进程:操作系统中同时运行的多个程序。

1.2线程的概念
是指进程中的一个执行任务(控制单元),是进程中的单个顺序控制流,是程序的执行单元,是一条执行路径。线程的执行具有随机性。

多线程:在同一个进行中同时运行的多个任务,并且多个线程可以共享数据。

如果一个程序(进程)只有一条执行路径,那么该程序就是单线程程序。
如果一个程序(进程)有多条执行路径,那么该程序就是多线程程序。
1.3 自定义线程
start方法:start方法是启动线程,一个线程一旦启动,便是独立的,**与主方法同级别。**只用调用了线程对象的start方法才会开启一个新的线程。
如果同一个线程对象相继调用两次start方法,会触发:IllegalThreadStateException:非法的线程状态异常。
要想调用两次线程,就需要定义两个线程对象,再分别调用start方法。
run方法:如果是直接调用对象的run方法不会开启新的线程,只是一个单线程。不能使用run方法启动一个新的线程。直接调用自定义线程对象的run方法是单线程的。要用start方法启动线程,再由jvm去调用该线程的run方法。
当一个线程启动后(调用了start方法),run方法的调用由CPU决定。
一旦一个线程启动之后就是一个独立的线程,等待着CPU的调度分配资源,不会因为启动它的外部线程结束而结束。
run方法和start方法的区别:run方法直接调用仅仅是普通方法。start方法先启动线程,再由jvn调用run方法。
1.3.1 继承自Thread类
public class MyThread extends Thread {
@Override
public void run() {
//重写run方法
}
}
public class Test {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();//启动线程
//注意:如过mt.start()这么些,就是直接调用mt对象的run方法而已。
//与线程的启动与否没有关系。
}
}
1.3.1 实现Runnable接口
public class MyRunnable implements Runnable {
@Override
public void run() {
//重写run方法
}
}
public class Test {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
Thread th = new Thread(mr);
th.start();
}
}

2.多线程安全问题
在我们运行一个简单的java程序的时候,就已经存在了两个线程,一个是主线程、一个是后台维护的垃圾回收线程。主线程很特殊,不用手动去调用start方法,它是在启动jvm的时候自动启动的。

当多个线程访问同一个数据源的时候,会出现线程安全问题。

这个案例可以通过多个多个售票窗口来出售同一列火车的座次车票来进行演示。假设有三个售票窗口在出售同一列火车的车票,那么这个车票便是这单个售票窗口共同的数据源,同一个座位是不不能在三个窗口卖给三个不同的旅客的。但是计算机在线程之间进行切换的时候,具有随机性。为了防止这种随机性,我们需要对线程进行同步。

导致线程出现安全性的原因:1、多线程访问出现网络延迟;2、线程的随机性。

  1. 线程同步的三种方式
    3.1 同步代码块
    同步代码块就在方法中的某一段代码块上加上一个锁,使得这一块代码在同一时间只能有同一个线程去访问。

synchronized (mutex) {
//需要被保护起来的代码
}
//mutex:同步监听对象,可以是任意一个对象。

同步监听对象的基本要求:

同步监听对象:必须保证自身就是同步监听对象。

同步监听对象:必须保证多个线程访问的是同一个同步监听对象,才能达到线程安全

同步监听对象可以是任意一个对象。

可以是this(注意继承自Thread类的方式)。

当前类的字节码对象Class。

Class cla = 对象名.getClass();
Class cla = 类名.class();

String常量池字符串对象。

3.1.1 继承自Thread类
public class TicketThread extends Thread {
//为了保证每个对象共享数据,将ticket设置成静态
static int ticket = 10;
public TicketThread_sync(String name) {
super(name);
}
@Override
public void run() {
while (ticket>0) {
//synchronized (this) {
/**
* 此处的锁对象就不能使用this,因为在TicketTest中,
* new出了三个TicketThread_sync对象,
* this就指向了三个不同的地址
* 不符合锁对象的同一性。
*/
synchronized (TicketThread_sync.class) {
if(ticket>0){
System.out.println(getName()+":您的票号是:"+ticket);
ticket–;
}
}
}
}
}
public class TicketTest {
public static void main(String[] args) {
TicketThread_sync tt1 = new TicketThread_sync(“窗口1”);
TicketThread_sync tt2 = new TicketThread_sync(“窗口2”);
TicketThread_sync tt3 = new TicketThread_sync(“窗口3”);
tt1.start();
tt2.start();
tt3.start();
}
}

3.1.2实现Runnable接口
public class TicketRunnable implements Runnable {
//因为使用的是同一个对象构建的三个线程,数据已经实现共享,不用加static修饰
int ticket = 10;;
@Override
public void run() {
while (ticket>0) {
/** 此处用this,指代的是TicketRunnable_sync的对象
* 在TicketTest的main方法中,我们通过统一个 TicketRunnable_sync对象
* 构建了三个线程对象 ,
* 这三个线程对象的锁-this指向的都是同一个TicketRunnable_sync对象
* 所以能够实现锁对象的不变,满足同一性。
*/
synchronized (this) {
if(ticket>0){
System.out.println(Thread.currentThread().getName()+":您的票号是:"+ticket);
ticket–;
}
}
}
}
}
public class TicketTest {
public static void main(String[] args) {
TicketRunnable_sync tr = new TicketRunnable_sync();
Thread t1 = new Thread(tr,“窗口一”);
Thread t2 = new Thread(tr,“窗口二”);
Thread t3 = new Thread(tr,“窗口三”);
t1.start();
t2.start();
t3.start();
}
}

3.2同步方法
在Java中也可以使用synchronized来同步方法,synchronized必须放在方法返回值前面,与public的位置可以互换。

3.2.1 继承自Thread类
public class TicketThread extends Thread {
static int ticket = 80;
public TicketThread(String name){
super(name);
}
@Override
public void run() {
while (ticket>0) {
saleTicket();
}
}
/**
* 注意:synchronized修饰方法也是有监听对象的。
* 如果修饰静态方法(同步静态方法),同步监听对象是当前类的字节码对象。
* 如果修饰非静态方法,同步非静态的方法:同步监听对象是this。
* 但是我们在测试的时候new了三个线程对象,this指向了三个不同的地址,不符合监听对象的同一性。
*/
public synchronized static void saleTicket(){
if(ticket>0){
System.out.println(Thread.currentThread().getName()+":您的票号是:"+ticket);
ticket–;
}
}
}
public class TicketTest {
public static void main(String[] args) {
TicketThread tt1 = new TicketThread(“窗口1”);
TicketThread tt2 = new TicketThread(“窗口2”);
TicketThread tt3 = new TicketThread(“窗口3”);
tt1.start();
tt2.start();
tt3.start();
}
}

3.2.2 实现Runnable接口
public class TicketRunnable implements Runnable {
int ticket = 10;
@Override
public void run() {
while (ticket>0) {
saleTicket();
}
}
public synchronized void saleTicket(){
if(ticket>0){
System.out.println(Thread.currentThread().getName()+":您的票号是:"+ticket);
ticket–;
}
}
}
public class Test {
public static void main(String[] args) {
TicketRunnable_Sync_Methed tsm = new TicketRunnable_Sync_Methed();
Thread t1 = new Thread(tsm,“窗口1”);
Thread t2 = new Thread(tsm,“窗口2”);
Thread t3 = new Thread(tsm,“窗口3”);
t1.start();
t2.start();
t3.start();
}
}

3.3lock对象
在API中查询Lock类,提示的使用方式为:

 Lock l = ...;//Lock是一个接口,我们需要new其实现类:ReentrantLock 
 l.lock();//打开锁
 try {
     // access the resource protected by this lock
 } finally {
     l.unlock();//关闭锁
 }
//采用finally这种模式,无论什么情况下都要关闭锁,避免锁死。

3.3.1 继承自Thread类
public class TicketThread extends Thread {
static int ticket = 10;
public TicketThread(String name){
super(name);
}
//为了保证每个线程对象使用的是同一把锁,加上static
static Lock lock = new ReentrantLock();
@Override
public void run() {
while(ticket>0){
saleTicket();
}
}
private void saleTicket() {
lock.lock();
try {
if (ticket>0) {
System.out.println(Thread.currentThread().getName()+":您的票号是:"+ticket);
ticket–;
}
} finally {
lock.unlock();
}
}
}
public class TicketTest {
public static void main(String[] args) {
TicketThread tt1 = new TicketThread(“窗口1”);
TicketThread tt2 = new TicketThread(“窗口2”);
TicketThread tt3 = new TicketThread(“窗口3”);
tt1.start();
tt2.start();
tt3.start();
}
}

3.3.2 实现Runnable接口
public class TicketRunnable implements Runnable {
int ticket = 10;
Lock lock = new ReentrantLock();
@Override
public void run() {
while (ticket>0) {
saleTicket();
}
}
public void saleTicket() {
lock.lock();
try {
if(ticket>0){
System.out.println(Thread.currentThread().getName()+":您的票号是:"+ticket);
ticket–;
}
} finally {
lock.unlock();
}
}
}
public class TicketTest {
public static void main(String[] args) {
TicketRunnable tr = new TicketRunnable();
Thread t1 = new Thread(tr,“窗口1”);
Thread t2 = new Thread(tr,“窗口2”);
Thread t3 = new Thread(tr,“窗口3”);
t1.start();
t2.start();
t3.start();
}
}

4.线程调度
假如计算机只有一个CPU(单核),那么CPU在某一个时刻只能执行一条指令,线程只得到CPU时间片,也就是使用权,(也称为执行权)才可以执行指令。线程的调度模型一般有两种:

分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片。
抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU的时间片相对多一些。
Java使用的就是抢占式调度模型。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值