1、如何在自定义的代码中,自定义一个线程呢?java
经过对api的查找,发现java已经提供了对线程这类事物的描述。就是Thread类api
2、Thread类安全
线程 是程序中的执行线程。Java 虚拟机容许应用程序并发地运行多个执行线程。多线程
public class Thread implements Runnable { //变量------------------------------------- private Runnable target; private char name[]; //字段------------------------------------- //线程能够具备的最低优先级。 public final static int MIN_PRIORITY = 1; //分配给线程的默认优先级。 public final static int NORM_PRIORITY = 5; //线程能够具备的最高优先级。 public final static int MAX_PRIORITY = 10; //构造方法---------------------------------- //1,构造方法:分配新的 Thread 对象。 public Thread() { init(null, null, "Thread-" + nextThreadNum(), 0); } //2,构造方法 public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); } //3,构造方法 public Thread(String name) { init(null, null, name, 0); } //4,构造方法 public Thread(Runnable target, String name) { init(null, target, name, 0); } //方法--------------------------------------- //1,若是该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;不然,该方法不执行任何操做并返回。 public void run() { if (target != null) { target.run(); } } //2,使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 public synchronized void start() { if (threadStatus != 0 || this != me) throw new IllegalThreadStateException(); group.add(this); start0(); if (stopBeforeStart) { stop0(throwableFromStop); } } private native void start0(); //3,返回该线程的字符串表示形式,包括线程名称、优先级和线程组。 public String toString() { ThreadGroup group = getThreadGroup(); if (group != null) { return "Thread[" + getName() + "," + getPriority() + "," + group.getName() + "]"; } else { return "Thread[" + getName() + "," + getPriority() + "," + "" + "]"; } } //4,返回该线程的名称。 public final String getName() { return String.valueOf(name); } //5,返回该线程的标识符。 public long getId() { return tid; } //6,返回线程的优先级。 public final int getPriority() { return priority; } //7,中断线程。 public void interrupt() { //......... } //8,等待该线程终止。 //当a线程执行到了b线程的.join()方法时,a就会等待,等b线程都执行完,a才会执行 //join能够用来临时加入线程执行 public final void join() throws InterruptedException { join(0); } //等待该线程终止的时间最长为 millis 毫秒 public final synchronized void join(long millis)throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (millis == 0) { while (isAlive()) { wait(0);//等待方法继承自Object } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay);//等待方法继承自Object now = System.currentTimeMillis() - base; } } } //等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。 public final synchronized void join(long millis, int nanos)throws InterruptedException { } //9,将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。该方法必须在启动线程前调用。 //Thread t1=new Thread();t1.setDaemon(true); //这时t1就是守护线程,就至关于后台线程,当前台线程都结束时,它自动结束了,就像是前台线程的守护神同样 public final void setDaemon(boolean on) { checkAccess(); if (isAlive()) { throw new IllegalThreadStateException(); } daemon = on; } //10,改变线程名称,使之与参数 name 相同。 public final void setName(String name) { checkAccess(); this.name = name.toCharArray(); } //11,更改线程的优先级。(1-10默认是5) public final void setPriority(int newPriority) { //......... } //12,在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操做受到系统计时器和调度程序精度和准确性的影响。 public static native void sleep(long millis) throws InterruptedException; //在指定的毫秒数加指定的纳秒数内让当前正在执行的线程休眠(暂停执行),此操做受到系统计时器和调度程序精度和准确性的影响 public static void sleep(long millis, int nanos) throws InterruptedException //....通过运算得出最终millis sleep(millis); } //13,暂停当前正在执行的线程对象,并执行其余线程。 public static native void yield(); //14,返回对当前正在执行的线程对象的引用。 public static native Thread currentThread(); }
3、建立线程的第一种方式:继承Thread类并发
- 定义类继承Thread
- 重写Thread类中的run方法
- 目的:将自定义代码存储在run方法中,让线程运行
- 调用线程的start方法
- 该方法有两个做用:启动线程,调用run方法
class Demo extends Thread { public void run() { for(int x=0;x<60;x++) System.out.println("demo run:"+x); } } class ThreadDemo { public static void main(String[] args) { Demo d = new Demo(); d.start(); for(int x=0;x<60;x++) System.out.println("main run:--------"+x); } }
- 发现上面代码运行结果每一次都不一样
- 由于多个线程都在获取cpu的执行权,cpu执行到谁,谁就运行
- 明确一点,在某一个时刻,只能有一个程序在运行,(多核除外)
- Cpu在作着快速的切换,以达到看上去是同时运行的效果
- 咱们能够形象的把多线程的运行形容为在互相抢夺cpu的执行权
- 这就是多线程的一个特性:随机性。谁抢到谁执行,至于执行多久,cpu说了算
4、为何要覆盖run方法呢?函数
- Thread类用于描述线程
- 该类就定义了一个用于存储线程要运行的代码的功能,该存储功能就是run方法,
- 也就是说Thread类中的run方法,用于存储线程要运行的代码
- 示例:
class Demo extends Thread { Demo(String name) { super(name);//设置线程名称 } public void run() { for(int x=0;x<60;x++) System.out.println(Thread.currentThread().getName()+x); } } class ThreadDemo { public static void main(String[] args) { Demo d = new Demo("haha"); d.start(); Demo d1 = new Demo("heihei"); d1.start(); for(int x=0;x<60;x++) System.out.println(Thread.currentThread().getName()+"---"+x); } }
- 线程默认的名称:Thread-编号 该编号从 0 开始
- 设置线程名称: setName()或者构造函数(super())
5、建立线程的第二种方式:实现Runnable接口优化
- 定义类实现Runnable接口
- 覆盖Runnable接口中的run方法----》将线程要运行的代码存放在run方法中
- 经过Thread类创建线程对象
- 将Runnable接口的子类对象做为实际参数传递给Thread类的构造函数,
- 为何要将Runnable接口的子类对象传递给Thread的构造函数。
- 由于,自定义的run方法所属的对象是Runnable接口的子类对象。
- 因此要让线程去指向指定对象的run方法,就必须明确该run方法所属的对象
- 调用Thread类的start方法开启线程并调用Runnable接口子类的run方法
- Runnable接口
public interface Runnable { //此接口只有这么一个成员 /** *Runnable
接口应该由那些打算经过某一线程执行其实例的类来实现。 * 设计该接口的目的是为但愿在活动时执行代码的对象提供一个公共协议。 * 例如,Thread
类实现了Runnable
。 * 激活的意思是说某个线程已启动而且还没有中止。 * <p> * 此外,Runnable
为非Thread
子类的类提供了一种激活方式。 * 经过实例化某个Thread
实例并将自身做为运行目标, * 就能够运行实现Runnable
的类而无需建立Thread
的子类。 * @see java.lang.Thread#run() */ public abstract void run(); }RunnableThreadRunnableRunnableThreadThreadRunnableThread
6、实现方式和继承方式的区别?this
- 实现方式避免了单继承的局限性
- 继承Thread:线程代码存放在Thread子类run方法中
- 实现Runnable:线程代码存在接口的子类的run方法中
- 在定义线程时,建议使用实现方式
7、示例线程
/** 需求:买车票 */ class Ticket implements Runnable { private int tick = 100; public void run() { while(true) { if(tick > 0) { try{Thread.sleep(10);}catch(Exception e){} System.out.println(Thread.currentThread().getName()+"::"+tick); } } } } class ThreadDemo { public static void main(String[] args) { Ticket t = new Ticket(); new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); new Thread(t).start(); } }
- 结果可能会出现0 -1 -2 等错票
- 多线程的运行出现了安全问题
- 这里写Thread.sleep(10);是模仿进程特别多的时候出现的状况
8、同步代码块设计
- 问题缘由:当多条语句在操做共享数据时,一个线程对多条语句只执行了一部分,尚未执行完,另外一个线程参与进来执行,致使共享数据的错误
- 解决方法:对多条操做共享数据的语句,只能让一个线程都执行完,在执行过程当中,其余线程不能够参与执行
- Java对于多线程的安全问题提供了专业的解决方式:就是同步代码块
- 格式:
synchronized(Object) { //须要被同步的代码 }
- 因此上面的代码能够优化以下:
-
class Ticket implements Runnable { private int tick = 100; Object obj = new Object(); public void run() { while(true) { synchronized(obj) { if(tick > 0) { try{Thread.sleep(10);}catch(Exception e){} System.out.println(Thread.currentThread().getName()+"::"+tick--); } } } } }
- obj如同锁,持有锁的线程能够在同步中执行
- 没有持有锁的线程即便获取cpu的执行权,也进不去,由于没有获取锁
- 同步的前提:必需要有两个或者两个以上的线程使用同一个锁
- 好处:解决了多线程的安全问题
- 弊端:多个线程须要判断锁,较为消耗资源
9、同步函数
如:
class Bank { private int sum; public synchronized void add(int n) { sum += n; System.out.println("sum:"+sum); } }
- 只须要用synchronized修饰函数便可,很是简洁方便
- 同步函数就是把操做共享数据的语句封装起来,可是只能封装须要同步的函数,不然还不如用同步代码块呢!
- 同步函数用的是哪个锁呢?
- 函数须要被对象调用,那么函数都有一个所属对象引用,就是this
- 因此同步函数使用的锁是this
- 能够经过程序来进行验证
- 若是同步函数被静态修饰后,使用的锁是什么呢?
- 经过验证,发现再也不是this,由于静态方法中也不能够定义this
- 静态进内存时,内存中没有本类对象,可是必定有该类对应的字节码文件对象—类名.class 该对象的类型是Class
- 静态的同步方法,使用的锁是该方法所在类的字节码文件对象,类名.class
10、死锁
同步中嵌套同步的时候,容易发生死锁