2018-06-11 第三十六天

简介:

一、线程的两种实现

1:继承线程类 java.lang.Thread:

优点:容易理解、代码实现也比较简单。

缺点:多个线程对象共享同一个数据,相对复杂、已经继承了Thread ,不能再继承其他的类。

 

2:实现接口  java.lang.Runnable:

优点:多个线程共享同一个数据,相对简单。可以再继承其他的类。

缺点:不容理解,使用了代理模式,代码实现相对复杂。

 

1:使用继承Thread 类,来模拟火车站售票系统。

/**

 * 继承线程Thread 类,来模拟火车站售票。

 * 每一个售票窗口都代表了一个线程。

 * 所有的售票员,都访问同一个售票系统。

 *

 */

public class TestTicket {

 

public static void main(String[] args) {

TicketSystem system = new TicketSystem();

Saler saler1 = new Saler(system);

saler1.setName("售票员小姐姐-1");//设置线程的名字

Saler saler2 = new Saler(system);

saler2.setName("售票员小姐姐-2");//设置线程的名字

Saler saler3 = new Saler(system);

saler3.setName("售票员小姐姐-3");//设置线程的名字

Saler saler4 = new Saler(system);

saler4.setName("售票员小姐姐-4");//设置线程的名字

Saler saler5 = new Saler(system);

saler5.setName("售票员小姐姐-5");//设置线程的名字

saler1.start();

saler2.start();

saler3.start();

saler4.start();

saler5.start();

}

}

 

//售票系统

class TicketSystem{

//剩余票数

private int count = 100;

/**

 * @param count 本次卖票的张数

 * @return 是否售票成功

 */

public boolean sale(int count){

if(this.count >= count){

this.count -= count;

System.out.println(Thread.currentThread().getName() + " 卖出了 "+count+" 张票,剩余票数为:"+this.count);

//让当前线程等待一下下 100

try {

Thread.sleep(100);

} catch (InterruptedException e) {

e.printStackTrace();

}

return true;

}

System.out.println(Thread.currentThread().getName() + " 售票失败 ,余票不足 ,剩余票数为:"+this.count);

return false;

}

}

 

//售票员

class Saler extends Thread{

//售票员需要访问唯一的票务系统

//多个线程对象,访问同一个对象的数据,可以使用同时持有唯一对象引用的方法。

private TicketSystem system;

public Saler(TicketSystem system) {

this.system = system;

}

//线程任务的主体部分。

public void run() {

boolean result = system.sale(1);

while(result){

result = system.sale(1);

}

}

}

 

、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、

2:Runnable 

/**

 * 使用实现Runnable 接口的方式,实现线程

 *

 */

public class RunnableTest {

 

public static void main(String[] args) {

TicketRunnable target =new TicketRunnable(/*system*/);

Thread saler1 = new Thread(target , "售票员小姐姐-1");

Thread saler2 = new Thread(target , "售票员小姐姐-2");

Thread saler3 = new Thread(target , "售票员小姐姐-3");

Thread saler4 = new Thread(target , "售票员小姐姐-4");

Thread saler5 = new Thread(target , "售票员小姐姐-5");

saler1.start();

saler2.start();

saler3.start();

saler4.start();

saler5.start();

}

}

 

class TicketRunnable implements Runnable{

//持有票务系统的引用

private TicketSystem system = new TicketSystem();

public TicketRunnable(/* TicketSystem system*/) {

// this.system = system;

}

 

//线程的主体功能

public void run() {

boolean result = system.sale(1);

while(result){

result = system.sale(1);

}

}

}

 

二、线程的生命周期

a92633ce9afdbcfb6adcfe4f349274585d7f5272

 

三、priority线程的优先级:

Thread 类中两个方法:

得到线程的优先级:int getPriority()

设置线程的优先级:void setPriority(int priority)

 

线程优先级的大小:

/**

     * The minimum priority that a thread can have.

     */

    public final static int MIN_PRIORITY = 1;

   /**

     * The default priority that is assigned to a thread.

     */

    public final static int NORM_PRIORITY = 5;

    /**

     * The maximum priority that a thread can have.

     */

    public final static int MAX_PRIORITY = 10;

 

如果设置线程的优先级 不在[1-10]之间,那么会抛出一个非法参数异常。

当一个线程创建之后,默认的优先级是 5。

主线程 main 的优先级5。

优先级比较小的线程,被cpu 调度执行的概率要低一些。

 

jvm 中的 垃圾回收器 工作由一个单独的线程来处理。该线程的优先级是所有的工作线程中最低的。所以垃圾回收器工作的时机不确定。

 

public class ThreadPriorityTest {

public static void main(String[] args) {

PriorityThread thread = new PriorityThread();

//先设置优先级后启动线程

thread.setPriority(Thread.MAX_PRIORITY);

thread.start();

int counter = 0 ;

while(true){

System.out.println(Thread.currentThread().getName() + "-->"+counter ++);

}

}

}

 

class PriorityThread extends Thread{

@Override

public void run() {

int counter = 0 ;

while(true){

System.out.println(Thread.currentThread().getPriority() + "-->"+counter ++);

}

}

}

 

四、join

void join() :等待该线程终止。

void join(long millis) :等待该线程终止的时间最长为 millis 毫秒。增加了一个解除阻塞原因的条件。(线程终止  时间到了)

void join(long millis, int nanos) :等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。

 

public class JoinTest {

public static void main(String[] args) throws Exception{

//新建状态

ChengThread thread = new ChengThread();

thread.setName("半路杀出个程咬金!");

for(int i=0;i<10;i++){

if(i == 5){

//就绪状态  等待cpu 调度

thread.start();

//导致当前线程(谁执行这句代码谁就是当前线程)进入阻塞状态

//立即执行 插队的线程(从就绪状态进入运行状态),

// 插队线程执行完毕之后,阻塞原因解除,被阻塞的线程从

//阻塞状态进入就绪状态,等待cpu 的下次调度。

//start  join

thread.join();

}

System.out.println(Thread.currentThread().getName() + "-->"+ i);

}

}

}

 

class ChengThread extends Thread{

@Override

public void run() {

for(int i=0;i<10;i++){

System.out.println(Thread.currentThread().getName() + "-->"+ i);

}

}

}

 

五、sleep

Thread.sleep(mills);

让当前线程进入阻塞状态(休眠),阻塞状态的时间为指定的时间(毫秒)。从进入休眠时开始倒计时,经历指定的休眠时间之后,

该线程自动从阻塞状态,进入就绪状态。等待cpu下次调度。

 

import java.text.DateFormat;

import java.text.SimpleDateFormat;

import java.util.Date;

 

public class SleepTest {

 

public static void main(String[] args) throws Exception {

test1();

test2();

}

//模拟倒计时

static void test1() throws Exception{

System.out.println("READY!");

for(int i=3;i>0;i--){

System.out.println(i);

Thread.sleep(1000);

}

System.out.println("GO!");

}

//计时器 每隔一秒输出一次当前时间 yyyy-MM-dd HH:mm:ss

//线程执行周期的时间精确控制

static void test2() throws Exception{

Date date = new Date();

DateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

while(true){

long time = System.currentTimeMillis();

String str = sdf.format(date);

System.out.println(str);

date.setSeconds(date.getSeconds()+1);

long cost = System.currentTimeMillis()-time;

Thread.sleep(1000-cost);

}

}

}

 

六、yield

yield():Thread 类的一个静态方法。

作用:让当前线程从运行状态进入就绪状态,等待cpu 的下次的调度。立即出让对cpu 的控制使用权。

 

public class YieldTest {

 

public static void main(String[] args) {

YieldThread thread1 = new YieldThread();

YieldThread thread2 = new YieldThread();

thread1.start();

thread2.start();

}

 

}

 

//理论的理想的状态应该是 2个线程交替打印

class YieldThread extends Thread{

public void run() {

for(int i=0;i<10;i++){

System.out.println(Thread.currentThread().getName() + "-->"+i);

//立即释放cpu 的控制权,进入就绪状态。

Thread.yield();

}

}

}

 

七、daemon

线程被分为两类:

1:用户线程  User Thread

2:守护线程  Daemon Thread

 

守护线程的特点:如果一个进程中存活的线程,只有守护线程了(不管有多少个守护线程),那么jvm退出。

进程的存活与否只与用户线程有关,只要一个进程中包含了一个存活的用户线程,那么该进程就不会被jvm 杀死。就是一个存活的进程。

线程创建之后,默认为用户线程,通过调用 thread.setDaemon(true);来设置 thread 对象为守护线程。

 

public final void setDaemon(boolean on)

将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。

该方法必须在启动线程前调用。

 

通过isDaemon() 来判断 线程对象是否为守护线程。

 

main 线程不能作为守护线程。

 

守护线程的作用:一般用来给用户线程提供服务的。

比如我们的 GC 线程。即为守护线程,用来给用户线程擦屁股的。如果用户线程都挂了。那么 GC 生无可恋,也就去了。

 

public class DaemonTest {

 

public static void main(String[] args) throws Exception{

DaemonThread thread = new DaemonThread();

//设置指定的线程为守护线程。

thread.setDaemon(true);

thread.start();

for(int i=0;i<10;i++){

System.out.println(Thread.currentThread().getName() + "-->" + i);

Thread.sleep(300);

}

}

 

}

 

class DaemonThread extends Thread{

@Override

public void run() {

while(true){

System.out.println("DaemonThread.run()");

try {

Thread.sleep(300);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

 

八、线程ID

每一个被创建的线程,都会被分配一个唯一的不同于其他线程的ID。

 

public long getId():返回该线程的标识符。线程 ID 是一个正的 long 数,在创建该线程时生成。线程 ID 是唯一的,并终生不变。线程终止时,该线程 ID 可以被重新使用。

 

可以通过线程的ID 来唯一的 区分 线程对象。

 

九、线程安全

线程安全的问题,是针对多线程的程序。单线程的情况下,是不存在线程安全问题。

 

产生线程安全问题的原因:多个线程同时访问同一块代码。但是实际上我们希望该代码块是原子性的,在某一个时间点,只希望一个线程单独访问,不希望多个线程同时访问。

 

解决方案

1:同步代码块。

synchronized (this) {

//被同步的代码块;

}

synchronized(同步监视器对象) java 关键字

{}:同步代码块:希望在某一个时间点只有一个线程访问的代码块。

 

执行过程:

1:线程1 执行到了同步代码块。要对同步监视器进行检测,看是否被其他的线程上锁了。发现没有上锁,那么线程1对同步监视器对象 上锁,并开始访问 同步代码块。

2:线程2 执行到了同步代码块,检测同步监视器,发现监视器已经被 线程1 上锁了。就进入就绪状态,等待cpu 下次调度执行。

3:线程1 执行完毕同步代码块,然后对 同步监视器对象  解锁。并执行后续的代码。

4:当 线程2 被再次调度执行的时候,发现同步监视器对象已经被解锁,那么就对同步监视器对象 加锁 并访问 同步代码块。

 

类似于上厕所:没人(有人就等着),进去,锁门,便,然后冲水开门出去。下一个。

 

2:同步方法:

相当于把整个方法体都同步了。

同步监视器对象  this(实例方法)。

同步方法,会导致在任意时刻,只能有一个线程访问该方法。一个线程执行完毕方法之后,其他的线程才能访问。

 

同步块和同步方法都会导致程序的效率降低。多了检测监视器对象是否加锁,加锁和解锁的过程。

 

关于监视器的选择:如果想实现线程间的互斥访问 同步代码块,那么监视器对象必须 唯一。只有一个实例存在。

 

/**

 * 模拟两个人取钱,说明线程安全的问题。

 * 张三和 张三媳妇,一起去取钱。

 * 一个在ATM

 * 一个在柜台。

 * 一共1500

 * 每个人都想取1000块钱。

 *

 */

public class AccountTest {

 

public static void main(String[] args) {

PersonRunnable runnable = new PersonRunnable();

Thread zhangSan = new Thread(runnable, "张三");

Thread zhangSanXiFu = new Thread(runnable, "张三媳妇");

zhangSan.start();

zhangSanXiFu.start();

}

}

 

/**

 * 账户类

 *

 */

class Account{

private int money = 1500;

/**

 * 取现

 * @param money  取钱的钱数

 * @return 如果取钱成功,返回 true ,否则返回 false

 */

public synchronized boolean withDrawMoney(int money){

//同步代码块

// synchronized (this) {

if(this.money >= money){

try {

Thread.sleep(10);

} catch (InterruptedException e) {

e.printStackTrace();

}

this.money -= money;

System.out.println(Thread.currentThread().getName() + "--> 取钱[成功]。余额为:"+this.money);

return true;

}

// }

System.out.println(Thread.currentThread().getName() + "--> 取钱[失败]。余额为:"+this.money);

return false;

}

}

 

class PersonRunnable implements Runnable{

//唯一的账户

private Account account = new Account();

 

public void run() {

account.withDrawMoney(1000);

}

}

 

316b531d9415e44320c6a978f1b95dbdac0f143b

目录
相关文章
星际争霸之小霸王之小蜜蜂(九)--狂鼠之灾
星际争霸之小霸王之小蜜蜂(九)--狂鼠之灾
星际争霸之小霸王之小蜜蜂(十六)--狂奔的花猫
星际争霸之小霸王之小蜜蜂(十六)--狂奔的花猫
星际争霸之小霸王之小蜜蜂(十三)--接着奏乐接着舞
星际争霸之小霸王之小蜜蜂(十三)--接着奏乐接着舞
星际争霸之小霸王之小蜜蜂(五)--为小蜜蜂降速
星际争霸之小霸王之小蜜蜂(五)--为小蜜蜂降速
|
存储 前端开发
草系前端手摸手带你实现正则引擎,点燃夏日最热情的烟火(三)🔥
草系前端手摸手带你实现正则引擎,点燃夏日最热情的烟火(三)🔥
109 0
|
前端开发
草系前端手摸手带你实现正则引擎,点燃夏日最热情的烟火(二)🔥
草系前端手摸手带你实现正则引擎,点燃夏日最热情的烟火(二)🔥
|
前端开发
草系前端手摸手带你实现正则引擎,点燃夏日最热情的烟火(一)🔥
草系前端手摸手带你实现正则引擎,点燃夏日最热情的烟火(一)🔥
165 0
|
存储 前端开发 开发者
「用前端重返童年🥤」为黑神话悟空定制红白机版游戏开始动画
「用前端重返童年🥤」为黑神话悟空定制红白机版游戏开始动画
285 0
端午还在“修锁”?你是真的卷王!(二)
今天为你带来的是 ReentrantLock 公平锁与非公平锁的源码分析,它是 Java 并发包下的一个 java.util.concurrent.locks 实现类,实现了 Lock 接口和 Serializable 接口。
端午还在“修锁”?你是真的卷王!(二)
下一篇
无影云桌面