一、如何在自定义的代码中,自定义一个线程呢?
通过对api的查找,发现java已经提供了对线程这类事物的描述。就是Thread类
二、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();
}
三、创建线程的第一种方式:继承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说了算
四、为什么要覆盖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())
五、创建线程的第二种方式:实现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();
}
六、实现方式和继承方式的区别?
- 实现方式避免了单继承的局限性
- 继承Thread:线程代码存放在Thread子类run方法中
- 实现Runnable:线程代码存在接口的子类的run方法中
- 在定义线程时,建议使用实现方式
七、示例
/**
需求:买车票
*/
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);是模仿进程特别多的时候出现的情况
八、同步代码块
- 问题原因:当多条语句在操作共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致共享数据的错误
- 解决方法:对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行
- 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的执行权,也进不去,因为没有获取锁
- 同步的前提:必须要有两个或者两个以上的线程使用同一个锁
- 好处:解决了多线程的安全问题
- 弊端:多个线程需要判断锁,较为消耗资源
九、同步函数
如:
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
十、死锁
同步中嵌套同步的时候,容易发生死锁