Java 通过继承 Thread 类来创建并启动多线程的步骤如下:
public class Thread1 extends Thread{
public static void main(String[] args) {
PrintNumber t1 = new PrintNumber();
t1.start();
}
}
class PrintNumber extends Thread{
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
if (i % 2 == 0){
System.out.println(i);
}
}
}
}
Java 有单继承的限制,当我们无法继承 Thread 类时,那么该如何做呢?在核心类库中提供了 Runnable 接口,我们可以实现 Runnable 接口,重写 run()方法,然后再通过 Thread 类的对象代理启动和执行我们的线程体 run()方法。
步骤如下:
public class Thread2{
public static void main(String[] args) {
//创建当前实现类的对象
EvenNumberPrint t1 = new EvenNumberPrint();
//将此对象作为参数传递到Thread类的构造器中,创建Thread类的实例
Thread thread = new Thread(t1);
//Thread类的实例调用start()方法
thread.start();
}
}
class EvenNumberPrint implements Runnable{
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
if (i % 2 == 0){
System.out.println(i);
}
}
}
}
建议使用实现Runnable接口的方式。因为①Runnable实现方式避免了类的单继承的局限性;②更适合处理有共享数据的问题;③实现了代码和数据的分离。
每个线程都有一定的优先级,同优先级线程组成先进先出队列(先到先服务),使用分时调度策略。优先级高的线程采用抢占式策略,获得较多的执行机会。每个线程默认的优先级都与创建它的父线程具有相同的优先级。Thread 类的三个优先级常量:
Java 语言使用 Thread 类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下一些状态:
线程的生命周期有五种状态:新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、死亡(Dead)。CPU 需要在多条线程之间切换,于是线程状态会多次在运行、阻塞、就绪之间切换。
当我们使用多个线程访问同一资源(可以是同一个变量、同一个文件、同一条记录等)的时候,若多个线程只有读操作,那么不会发生线程安全问题。但是如果多个线程中对资源有读和写的操作,就容易出现线程安全问题。Java使用线程的同步机制来解决线程的安全问题。
synchronized(同步监视器){
//需要被同步的代码
}
package p136;
public class WindowTest {
public static void main(String[] args) {
SaleTicket s = new SaleTicket();
Thread t1 = new Thread(s);
Thread t2 = new Thread(s);
Thread t3 = new Thread(s);
t1.start();
t2.start();
t3.start();
}
}
class SaleTicket implements Runnable {
int ticket = 100;
@Override
public void run() {
while (true) {
synchronized (this) {
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
如果操作共享数据的代码完整的声明在了一个方法中,那么我们就可以将此方法声明为同步方法即可。
package p136;
public class WindowTest1 {
public static void main(String[] args) {
SaleTicket s = new SaleTicket();
Thread t1 = new Thread(s);
Thread t2 = new Thread(s);
Thread t3 = new Thread(s);
t1.start();
t2.start();
t3.start();
}
}
class SaleTicket1 implements Runnable {
int ticket = 100;
boolean isFlag = true;
@Override
public void run() {
while (isFlag){
show();
}
}
public synchronized void show() { //此时的同步监视器为this
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);
ticket--;
} else {
isFlag = false;
}
}
}
package p138;
public class BankTest {
static Bank b1 = null;
static Bank b2 = null;
public static void main(String[] args) {
Thread t1 = new Thread() {
@Override
public void run() {
b1 = Bank.getInstance();
}
};
Thread t2 = new Thread() {
@Override
public void run() {
b2 = Bank.getInstance();
}
};
t1.start();
t2.start();
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(b1);
System.out.println(b2);
System.out.println(b1 == b2);
}
}
class Bank {
private Bank() {
}
private static volatile Bank instance = null;
//实现线程安全的方式1
public static synchronized Bank getInstance() { //同步监视器默认为Bank.class
if (instance == null) {
instance = new Bank();
}
return instance;
}
//实现线程安全的方式2
public static Bank getInstance() { //同步监视器默认为Bank.class
synchronized (Bank.class) {
if (instance == null) {
instance = new Bank();
}
return instance;
}
}
//实现线程安全的方式3,相较于方式1和方式2来讲,效率更高。
public static Bank getInstance() { //同步监视器默认为Bank.class
if (instance == null) {
synchronized (Bank.class) {
if (instance == null) {
instance = new Bank();
}
}
}
return instance;
}
}
/*
注意:上述方式 3 中,有指令重排问题
mem = allocate(); 为单例对象分配内存空间
instance = mem; instance 引用现在非空,但还未初始化
ctorSingleton(instance); 为单例对象通过 instance 调用构造器
从 JDK2 开始,分配空间、初始化、调用构造器会在线程的工作存储区一次性完成,然后复制到主存储区。但是需要volatile 关键字,避免指令重排。
*/
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。一旦出现死锁,整个程序既不会发生异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。
死锁一旦出现,基本很难人为干预,只能尽量规避。可以考虑打破上面的诱发条件。
class Window extends Thread{
static int ticket = 100;
//1.创建Lock的实例,需要确保多个线程共用同一个Lock实例,需要考虑将此对象声明为static final
private static final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try {
//2.执行lock方法,锁定对共享资源的调用
lock.lock();
if (ticket > 0){
try {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);
ticket--;
}
}
//3.执行unlock方法,释放对共享资源的调用
lock.unlock();
}
}
}
}
当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行,那么多线程之间需要一些通信机制,可以协调它们的工作,以此实现多线程共同操作一份数据。
这是多个线程间的一种协作机制。谈到线程我们经常想到的是线程间的竞争(race),比如去争夺锁,但这并不是故事的全部,线程间也会有协作机制。
注意点:
package p139;
/**
* 两个线程交替打印1-100之间的数
*/
public class PrintNumberTest {
public static void main(String[] args) {
PrintNumber p = new PrintNumber();
Thread t1 = new Thread(p,"线程1");
Thread t2 = new Thread(p,"线程2");
t1.start();
t2.start();
}
}
class PrintNumber implements Runnable{
private int number = 1;
@Override
public void run() {
while (true){
synchronized (this) {
notify(); //唤醒线程
if (number <= 100){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+ number);
number++;
try {
wait(); //线程一旦执行此方法,就进入等待状态,会同时释放对同步监视器的调用
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
break;
}
}
}
}
}
package p139;
/**
* 生产者和消费者问题
*/
/**
* 店员
*/
class Clerk{
private int product = 0; //产品数量
/**
* 增加产品数量
*/
public synchronized void addProduct(){
if (product >= 20){
//等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
product++;
System.out.println(Thread.currentThread().getName()
+"生产了第"+product+"个产品");
//唤醒
notifyAll();
}
}
/**
* 减少产品数量
*/
public synchronized void minusProduct(){
if (product <= 0){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
System.out.println(Thread.currentThread().getName()
+"消费了第"+product+"个产品");
product--;
//唤醒
notifyAll();
}
}
}
/**
* 生产者
*/
class Producer extends Thread{
//将Clerk进行共享
private Clerk clerk;
//在创建Producer时,通过构造器实例化Clerk对象
public Producer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
while (true){
System.out.println("生产商品");
clerk.addProduct();
}
}
}
/**
* 消费者
*/
class Consumer extends Thread{
//将Clerk进行共享
private Clerk clerk;
//在创建Consumer时,通过构造器实例化Clerk对象
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
while (true){
System.out.println("消费商品");
clerk.minusProduct();
}
}
}
public class ProducerAndConsumerTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer pro1 = new Producer(clerk);
Consumer con1 = new Consumer(clerk);
pro1.setName("生产者1");
con1.setName("消费者1");
pro1.start();
con1.start();
}
}
public class ThreadPool {
public static void main(String[] args) {
//1.提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//设置线程池的属性
service1.setMaximumPoolSize(50);
//2.执行指定的线程的操作,需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread()); //适用于Runnable
service.execute(new NumberThread1()); //适用于Runnable
service.submit(Callable callable); //适用于Callable
//3.关闭连接池
service.shutdown();
}
}