多线程
通过继承Thread类实现多线程
Thread类存放在java.lang类库中,无需显示加载
Thread类中,已经定义了run()
方法,如果想要实现多线程,必须定义自己的子类,继承与Thread类,同时要重写Thread类的run方法,然后用用户自定义的线程类,生成对象,并调用该对象的start()
方法,从而来激活一个线程。
public class ThreadDemo_1 {
public static void main(String[] args) {
new TestThread().start();
for(int i = 0; i < 5; i++) {
System.out.println("main 线程运行");
try {
Thread.sleep(1000);
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
}
class TestThread extends Thread{
public void run() {
for(int i = 0; i < 5; i++) {
System.out.println("Thread 线程运行");
try {
Thread.sleep(1000);
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
}
通过实现Runnable接口实现多线程
public class RunnableThread {
public static void main(String[] args) {
TestThread2 newTh = new TestThread2();
new Thread(newTh).start(); // 使用Thread类的start方法启动线程
for(int i = 0; i < 5; i++) {
System.out.println("main 线程运行");
try {
Thread.sleep(1000);
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
}
class TestThread2 implements Runnable{
public void run() {
for(int i = 0; i < 5; i++) {
System.out.println("TestThread2 线程运行");
try {
Thread.sleep(1000);
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
}
Thread和Runnable两种多线程机制的比较
- Thread类实现了Runnable接口,本质上,Thread类是Runnable接口众多的实现子类中的一个
- 接口是功能的集合,只要实现了Runnable接口,就具备了可执行的功能
public class ThreadDemo {
public static void main(String[] args) {
TestThread3 newTh = new TestThread3();
newTh.start();
newTh.start();
newTh.start();
newTh.start();
}
}
class TestThread3 extends Thread{
private int tickets = 5;
public void run() {
while(tickets > 0) {
System.out.println(Thread.currentThread().getName() + "出售票" + tickets);
tickets -= 1;
}
}
}
结果为:
Thread-0出售票5
Thread-0出售票4
Thread-0出售票3
Thread-0出售票2
Thread-0出售票1
Exception in thread "main" java.lang.IllegalThreadStateException
at java.lang.Thread.start(Unknown Source)
at ThreadDemo.ThreadDemo.main(ThreadDemo.java:7)
从运行结果来看,程序出现了异常,只有却只有一个线程在运行,这说明一个类继承了Thread类之后,这个类的实例化对象,无论调用多少次start()方法,结果都只有一个线程在运行
接下来我们来实现四个线程
public class ThreadDemo {
public static void main(String[] args) {
// TestThread3 newTh = new TestThread3();
// newTh.start();
// newTh.start();
// newTh.start();
// newTh.start();
new TestThread3().start();
new TestThread3().start();
new TestThread3().start();
new TestThread3().start();
}
}
class TestThread3 extends Thread{
private int tickets = 5;
public void run() {
while(tickets > 0) {
System.out.println(Thread.currentThread().getName() + "出售票" + tickets);
tickets -= 1;
}
}
}
结果为:
Thread-0出售票5
Thread-3出售票5
Thread-3出售票4
Thread-2出售票5
Thread-1出售票5
Thread-1出售票4
Thread-1出售票3
Thread-2出售票4
Thread-3出售票3
Thread-0出售票4
Thread-3出售票2
Thread-2出售票3
Thread-1出售票2
Thread-2出售票2
Thread-3出售票1
Thread-0出售票3
Thread-2出售票1
Thread-1出售票1
Thread-0出售票2
Thread-0出售票1
从结果看出,程序总共有4个线程,但是每个线程占有自己的资源,都有5张票,如果想要达到资源共享的目的,可以使私有变量tickets变成静态变量
下面来看一下Runnable接口的实现
public class RunnableDemo {
public static void main(String[] args) {
TestThread4 newTh = new TestThread4();
new Thread(newTh).start();
new Thread(newTh).start();
new Thread(newTh).start();
new Thread(newTh).start();
}
}
class TestThread4 implements Runnable{
private static int tickets = 5;
public void run() {
while(tickets > 0) {
System.out.println(Thread.currentThread().getName() + "出售票" + tickets);
tickets -= 1;
}
}
}
从结果看,Runnable达到了资源共享的目的
可见,实现Runnable接口相对于继承Thread类来说,有如下的优势:
- 避免了由于Java的单继承特性带来的局限性
- 可使多个线程共享相同的资源,以达到资源共享的目的
Java8中运行线程的新方法
Lamda表达式:
Thread thread = new Thread(()->{System.out.println("Java 8");}).start();
()->{System.out.println("Java 8");}
就是Lamda表达式Lamda表达式的机构可大体分为3个部分:
- 最前面的部分是一对括号,里面是参数,这里无参数,就是一对空括号
- 中间是
->
,用来分割参数和主体- 主体部分可以是一个表达式或者一个语句块,用花括号括起来,如果一个表达式,表达式的值会作为返回值放回。如果是语句块,需要用return语句指定返回值。
public class LamdaDemo {
public static void main(String[] args) {
Runnable task = ()->{
String threadName = Thread.currentThread().getName();
System.out.println("Hello " + threadName);
};
task.run();
Thread thread = new Thread(task);
thread.start();
System.out.println("Done!");
}
}
线程基本操作
- 获取线程名称
Thread t = Thread.currentThread();
t.getName();
- 设置线程名字
public class GetSetNameThreadDemo implements Runnable{
public void run(){
Thread temp = Thread.currentThread();
System.out.print(temp.getName());
}
public static void main(String[] args{
Thread t = new Thread(new GetSetNameThreadDemo());
t.setName("thread_test1");
t.start();
}
}
- 判断线程是否启动
Thread t = Thread.currentThread();
t.isAlive();
t.start();
t.isAlive();
守护线程与setDaemon方法
在JVM中,线程分为两类:用户进程和守护进程。
Thread源码中:
private boolean daemon = false;
这意味着,默认创建的线都是普通的用户线程,只有调用setDeamon(true)
之后,才能转成守护进程
线程的特点
- 同步代码块和同步方法锁的是对象,而不是代码,即如果某个对象被同步代码块或者同步方法锁住,那么其他使用该对象的代码必须等待,直到该对象的锁被释放。
- 如果有个进程只有后台线程,则这个进程就会结束
- 每一个已经被创建的线程在结束之前均会处于就绪、运行、阻塞状态之一