学习目标
1:线程的概念
2:Thread类和Runnable接口
3: 主线程与线程的生命周期
4:线程状态控制方法(sleep休眠状态)
5:线程安全threadsafe
学习内容
1:线程的概念
线程和进程的概念
进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。
线程是指进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。线程总是属于某个进程,进程中的多个线程共享进程的内存。
并行:多个CPU同时执行多个任务,比如:多个人同时做不同的事
并发:一个CPU(采用时间片)同时执行多个任务,比如秒杀平台,多个人做同件事
线程的相关API
Thread.currentThread().getName()
1.start():1.启动当前线程2.调用线程中的run方法
2.run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
3.currentThread():静态方法,返回执行当前代码的线程
4.getName():获取当前线程的名字
5.setName():设置当前线程的名字
6.yield():主动释放当前线程的执行权
7.join():在线程中插入执行另一个线程,该线程被阻塞,直到插入执行的线程完全执行完毕以后,该线程才继续执行下去
8.stop():过时方法。当执行此方法时,强制结束当前线程。
9.sleep(long millitime):线程休眠一段时间
10.isAlive():判断当前线程是否存活
2:Thread类和Runnable接口
thread类
Java中,创建线程的方法有两种:一是通过继承线程类Thread来创建线程;二是建立一个实现Runnable接口的类。
例如:
package Demo01;
public class demo01MainThread {
public static void main(String[] args) {
Person p1=new Person("小强");
p1.run();
Person p2=new Person("小刚");
p2.run();
}
}
package Demo01;
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Person(String name) {
this.name = name;
}
public Person() {
}
public void run() {
for(int i=0;i<20;i++) {
System.out.println(name+"-->"+i);
}
}
}
结果为:
例如2:
package Demo01;
public class Demo01Thread {
public static void main(String[] args) {
MyThread mt= new MyThread();
mt.start();
for (int i = 1; i <= 20; i++) {
System.out.println("主线程"+i);
}
}
}
package Demo01;
//创建thread类
public class MyThread extends Thread {
//重写run
@Override
public void run() {
for (int i = 1; i <= 20; i++) {
System.out.println("子线程:"+i);
}
}
}
结果为:
Runnable接口
从 NEW 到 RUNNABLE 状态
Java 刚创建出来的 Thread 对象就是 NEW 状态,而创建 Thread 对象主要有两种方法。一种是继承 Thread 对象,重写 run() 方法。示例代码如下:
// 自定义线程对象
class MyThread extends Thread {
public void run() {
// 线程需要执行的代码
…
}
}
// 创建线程对象
MyThread myThread = new MyThread();
另一种是实现 Runnable 接口,重写 run() 方法,并将该实现类作为创建 Thread 对象的参数。示例代码如下:
// 实现 Runnable 接口
class Runner implements Runnable {
@Override
public void run() {
// 线程需要执行的代码
…
}
}
// 创建线程对象
Thread thread = new Thread(new Runner());
步骤如下:1.定义类实现Runnable接口
2.覆盖Runnable接口中的run方法(将线程要运行的代码存放在run方法)
3.通过Thread类建立线程对象
4.将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数
(因为自定义的run方法所属的对象是Runnable接口的子类对象,所以要让线程去指定对象的run方法 ,就必须明确改run方法所属的对象)
5.调用Thread类的start方法开启线程并调用Runnable接口子类的run方法
和继承Thread的实现多线程的区别如下:
1.避免了单继承的局限性(在定义线程是建议使用Runnable接口实现)
2.存储位置不同(继承方式:线程代码存放Thread子类的方法中
实现Runnable 线程代码存放在接口的子类的run方法中
)
例如:
package Demo4;
public class Demo4Runnable {
public static void main(String[] args) {
Thread t = new Thread(new RunnableImpl2());
t.start();
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
package Demo4;
public class RunnableImpl {
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
package Demo4;
public class RunnableImpl2 implements Runnable{
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("helloworld"+i);
}
}
}
结果为:
3: 主线程与线程的生命周期
通用的线程生命周期基本上可以用下图这个“五态模型”来描述。这五态分别是:初始状态、可运行状态、运行状态、休眠状态和终止状态。
例如:
package Demo02;
import Demo01.MyThread;
public class demo01Thread {
public static void main(String[] args) {
MyThread mt= new MyThread();
mt.start();
new MyThread().start();
new MyThread().start();
new MyThread().start();
System.out.println("main:"+Thread.currentThread().getName());
}
}
package Demo02;
public class MyThreadName extends Thread {
public MyThreadName() {}
public MyThreadName(String name) {
super(name);
}
@Override
public void run ()
{
System.out.println("子:"+Thread.currentThread().getName());
}
}
结果为:
例如2:
package Demo02;
public class demo02ThreadSetName {
public static void main(String[] args) {
MyThreadName mt=new MyThreadName("小强");
mt.start();
new MyThreadName("旺财").start();
}
}
package Demo02;
public class MyThread extends Thread {
//重写run
@Override
public void run() {
System.out.println("子"+Thread.currentThread().getName());
}
}
结果为:
4:线程状态控制方法(sleep休眠状态)
sleep方法是Thread类中的一个静态方法,当一个执行中的线程调用了Thread的sleep方法之后,调用线程会暂时让出指定时间的执行权,这期间不参与CPU的调度,但是该线程所拥有的监视器资源,比如锁还是持有且不让出的。指定的睡眠时间到了之后,sleep函数会正常返回,线程就处于就绪状态,然后参与CPU调度,获取到CPU的资源后就可以运行了。
例如:
package Demo03;
public class demo03Sleep {
public static void main(String[] args) {
for(int i=1;i<50;i++)
{
System.out.println(i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
}
结果为:
5:线程安全threadsafe
线程之间存在“竞争条件”,作用于同一个mutable数据上的多个线程,彼此之间存在对该数据的访问竞争并导致interleaving,导致post-condition可能被违反,这是不安全的。
线程安全:ADT或方法在多线程中要执行正确。
要做到:不违反spec,保持RI。与多少处理器,如何调度线程无关,不需要在spec中强制要求client满足某种“线程安全”的义务。
例如:Iterator就不是线程安全的,因为你不能在迭代遍历的时候改变collection。
保证线程安全,有四种策略:
(1)限制数据共享(Confinement)
(2)共享不可变数据(Immutability)
(3)共享线程安全的可变数据(Threadsafe data type)
(4)同步机制(Synchronization)
例如:
package Demo05ThreadSafe;
public class demo01Ticket {
public static void main(String[] args) {
RunnableImpl run=new RunnableImpl();
Thread t0=new Thread(run);
Thread t1=new Thread(run);
Thread t2=new Thread(run);
t0.start();
t1.start();
t2.start();
}
}
package Demo05ThreadSafe;
/*
解决线程安全的一种方案:使用同步代码块
格式:
synchronized(锁对象){
可能会出现线程安全的代码(是因为访问了共享的数据)
}
注意:
1、通过代码块中的锁对象,可以使用任意的对象
2、但是必须保证多个线程使用的锁对象是同一个
3、锁对象作用:
把同步代码块锁住,只让一个线程执行
*/
public class RunnableImpl implements Runnable{
private int ticket = 100;
Object obj=new Object();
@Override
public void run() {
while (true) {
synchronized (obj) {
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "--->正在卖第" + ticket + "张票");
ticket--;
}
}
}
}
}
结果为:
202080605041