文章目录
前言
无论是学习还是找工作,多线程都是重点,今天在这里分享一些多线程的基本知识,希望大家跟我一起学习。
一、进程跟线程有什么区别?
在学习多线程之前,我们需要弄清楚几个概念,什么是进程?什么是线程?这两者有什么区别跟联系?
- 进程是指正在运行的程序,它是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程;
- 线程是CPU调度和分派的基本单位,是可以独立运行的基本单位,同一进程中的多个线程可以并发执行;
- 进程在执行过程中拥有独立的内存单元,而多个线程共享内存单元,减少切换次数,从而效率更高。
二、如何创建线程?
1.继承Thread类
创建线程的第一种方式就是继承Thread类,并重写run()方法,创建一个子类对象,子类对象通过调用start()方法启动线程。注意,启动线程使用start()方法,而不是直接调用run()方法,直接调用就和普通方法一样。代码演示如下:
public class Thread01 {
public static void main(String[] args) {
MyThread01 t1 = new MyThread01();
MyThread02 t2 = new MyThread02();
t1.setName("线程一");
t2.setName("线程二============");
t1.start();
t2.start();
}
}
class MyThread01 extends Thread {
@Override
public void run() {
for(int i = 0; i < 10; i++) {
if(i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + "=" + i);
}
}
}
}
class MyThread02 extends Thread {
@Override
public void run() {
for(int i = 0; i < 10; i++) {
if(i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + "=" + i);
}
}
}
}
运行结果如下:
2.实现Runnable接口
在java中,一个类只可以继承父类,但是可以实现多个接口,如果某一个类已经继承了其他类就无法通过继承Thread类来创建线程,这个时候我们可以通过实现Runnable接口,然后重写run()方法,创建一个子类对象,并将此对象作为参数来创建一个Thread类的对象,然后通过start()方法开始线程。代码演示如下
public class Thread02 {
public static void main(String[] args) {
MyThread03 myThread03 = new MyThread03();
Thread t3 = new Thread(myThread03);
MyThread04 myThread04 = new MyThread04();
Thread t4 = new Thread(myThread04);
t3.setName("线程3");
t4.setName("线程4============");
t3.start();
t4.start();
}
}
class MyThread03 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + "=" + i);
}
}
}
}
class MyThread04 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + "=" + i);
}
}
}
}
运行结果如下:
3.实现Callable接口
Java5.0在Java.util.concurrent提供了一个新的创建执行线程的方式:实现Callable接口,并重写call()方法,大家要注意,这次不是重写run()方法,而是带有返回值的call()方法,需要借助FutureTask类来开启线程和获取返回结果。关于Future接口以及它的唯一实现类的说明放在代码注释中,具体代码实现如下:
/**
Future接口可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等;
FutureTask是Futrue接口的唯一的实现类;
FutureTask 同时实现了Runnable, Future接口;
它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
*/
public class Thread03 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
MyThread05 myThread05 = new MyThread05();
MyThread06 myThread06 = new MyThread06();
FutureTask<Integer> futureTask5 = new FutureTask<Integer>(myThread05);
FutureTask<Integer> futureTask6 = new FutureTask<Integer>(myThread06);
Thread thread5 = new Thread(futureTask5, "方式5");
Thread thread6 = new Thread(futureTask6, "方式6=======");
thread5.start();
thread6.start();
System.out.println("方式5的和" + futureTask5.get());
System.out.println("方式6的和" + futureTask6.get());
}
}
class MyThread05 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for(int i = 0; i < 10; i++) {
if(i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + "=" + i);
sum += i;
}
}
return sum;
}
}
class MyThread06 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for(int i = 0; i < 10; i++) {
if(i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + "=" + i);
sum += i;
}
}
return sum;
}
}
运行结果如下:
4.通过线程池创建线程
线程池在下一篇博客详细说明,代码如下:
public class Thread04 {
public static void main(String[] args) {
//线程池
ExecutorService pool = Executors.newFixedThreadPool(10);
ThreadPoolExecutor executor = (ThreadPoolExecutor) pool;
//开启线程
MyThread07 myThread07 = new MyThread07();
MyThread07 myThread08 = new MyThread07();
executor.execute(myThread07);
executor.execute(myThread08);
}
}
class MyThread07 implements Runnable{
@Override
public void run() {
for(int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "=" + i);
}
}
}
运行结果如下:
三、线程有哪几种状态?
线程是一个动态执行的过程,它也有一个从产生到死亡的过程。下图显示了一个线程完整的生命周期。
四、什么是线程不安全?
线程不安全是指不提供加锁保护机制保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。举个例子,售票系统有100张票,A和B同时来买票,如果线程不安全,就会发生100-1这个操作,同时执行,结果就是A和B都买到票了,然后系统中余票显示还有99张。代码演示如下:
public class Thread05 {
public static void main(String[] args) {
Count count = new Count();
Thread t1 = new Thread(count,"线程1======");
Thread t2 = new Thread(count,"线程2");
t1.start();
t2.start();
}
}
class Count implements Runnable {
private int count = 100;
@Override
public void run() {
while (true) {
if (count == 0) {
break;
}
try {
//增加出现线程不安全情况的几率,为了演示方便
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName() + "(" + count + ")");
}
}
}
运行结果如下:
那么要如何解决呢?可以采用加锁保护机制,代码如下:
public class Thread06 {
public static void main(String[] args) {
Count1 count = new Count1();
Thread t1 = new Thread(count, "线程1======");
Thread t2 = new Thread(count, "线程2");
t1.start();
t2.start();
}
}
class Count1 implements Runnable {
private int count = 100;
@Override
public void run() {
while (true) {
synchronized (this) {
if (count == 0) {
break;
}
try {
//增加出现线程不安全情况的几率,为了演示方便
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName() + "(" + count + ")");
}
}
}
}
使用synchronized 关键字将售票这个过程“锁”起来,就不会造成两个人都买到票,但是票数只减了一张这种情况的发生。运行结果如下:
五、线程的常用方法?
下面列举一些Thread类的实例对象的一些重要方法
方法名 | 方法描述 |
---|---|
start() | 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 |
run() | 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。 |
setName(String name) | 改变线程名称,使之与参数 name 相同。 |
setPriority(int priority) | 更改线程的优先级。 |
setDaemon(boolean on) | 将该线程标记为守护线程或用户线程。 |
join(long millisec) | 等待该线程终止的时间最长为 millis 毫秒。 |
interrupt() | 中断线程。 |
isAlive() | 测试线程是否处于活动状态。 |
下面列举一些Thread类的一些静态方法
静态方法名 | 方法描述 |
---|---|
yield() | 暂停当前正在执行的线程对象,并执行其他线程。 |
sleep(long millisec) | 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行) |
holdsLock(Object x) | 当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。 |
currentThread() | 返回对当前正在执行的线程对象的引用。 |
dumpStack() | 将当前线程的堆栈跟踪打印至标准错误流。 |
总结
Good Good Study,Day Day Up!