程序是一组计算机指令的有序集合,这些指令告诉计算机如何执行特定的任务
进程是指正在运行中的程序实例。一个程序被执行时,系统会为该程序创建一个进程,进程包括该程序的代码、数据、堆栈等信息。每个进程都有自己的独立内存空间,它与其他进程的内存空间是隔离的。在操作系统中,进程是调度和分配资源的基本单位。
线程是计算机程序中的一个执行序列。它是进程中的一个单独的控制流,独立地运行于其他线程之上。线程共享进程的资源,如内存、文件句柄、网络连接等,但每个线程有自己的程序计数器、堆栈和局部变量。多线程可以提高程序的并发性和效率,因为它们可以同时执行多项任务。
void run()
在线程开启后,此方法将被调用执行
void start()
使此线程开始执行,Java虚拟机会调用run方法()
定义一个类MyThread继承Thread类
在MyThread类中重写run()方法
package 线程的创建;
public class MyThread extends Thread{
@Override
public void run() {
System.out.println("我是线程!");
}
}
MyThread myThread = new MyThread();
myThread.start();
注意: 在MyThread类中重写run()方法是为了实现自定义的线程执行逻辑。因为每个线程都有自己的run()方法,所以可以根据实际需求重写这个方法,从而使每个线程执行不同的任务。
简单地说,run()是用来封装被线程执行的代码
Thread(Runnable target)
分配一个新的Thread对象
Thread(Runnable target, String name)
分配一个新的Thread对象
定义一个类MyRunnable实现Runnable接口
在MyRunnable类中重写run()方法
package 线程的创建;
public class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("我是线程!");
}
}
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
void setName(String name)
将此线程的名称更改为等于参数name
String getName()
返回此线程的名称
Thread currentThread()
返回对当前正在执行的线程对象的引用
代码示例:
package 获取当前线程名称和设置名称;
public class Demo {
public static void main(String[] args) {
Thread t1=Thread.currentThread();
System.out.println(t1);
System.out.println(t1.getName());
t1.setName("lll");
System.out.println(t1.getName());
}
}
static void sleep(long millis)
使当前正在执行的线程停留(暂停执行)指定的毫秒数
代码示例:
package 线程休眠;
public class Demo {
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
for (int i=0;i<10;i++){
try {
Thread.sleep(1000);
System.out.println(i);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}.start();
}
}
Java中的线程优先级是一个整数值,是一个从1到10之间的数字,其中1表示最低的优先级,10表示最高的优先级。Java中线程优先级的概念是用来帮助操作系统在调度线程时做出决策的。默认情况下,所有线程的优先级都是5。
需要注意的是,线程优先级的设置只是给操作系统一个提示,而不是一个指令。操作系统可能不会完全遵循这个提示,并根据其他因素(如线程的状态、负载平衡等)来调度线程。因此,我们不应该过度依赖线程优先级来保证线程的执行顺序和效率。(简单地说,线程优先级越大,被执行可能性越大)
final int getPriority()
返回此线程的优先级
final void setPriority(int newPriority)
更改此线程的优先级线程默认优先级是5;线程优先级的范围是:1-10
代码示例:
package 线程优先级;
public class Demo {
public static void main(String[] args) {
Thread t1= new Thread(){
@Override
public void run() {
for (int i=0;i<1000;i++) {
System.out.println("t1线程"+i);
}
}
};
Thread t2= new Thread(){
@Override
public void run() {
for (int i=0;i<1000;i++) {
System.out.println("t2线程"+i);
}
}
};
System.out.println(t1.getPriority());
System.out.println(t2.getPriority());
t1.setPriority(10);
t2.setPriority(1);
t1.start();
t2.start();
}
}
默认是5,将t1线程优先级设为10,t2线程设为1后,系统会优先概率先进行t1线程,可以自行运行看看。
void setDaemon(boolean on)
将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出
示例代码:
package 线程优先级;
public class Demo {
public static void main(String[] args) {
Thread t1= new Thread(){
@Override
public void run() {
for (int i=0;i<100;i++) {
System.out.println("t1线程"+i);
}
}
};
Thread t2= new Thread(){
@Override
public void run() {
for (int i=0;i<1000;i++) {
System.out.println("t2线程"+i);
}
}
};
t2.setDaemon(true);
t1.start();
t2.start();
}
}
final void join()
等待这个线程死亡。
final void join(long millis)
final void join(long millis)
等待这个线程死亡的时间最多为millis毫秒。 0的超时意味着永远等待。
final void join(long millis, int nanos)
等待最多millis毫秒加上这个线程死亡的nanos纳秒
package 加入线程;
public class Demo {
public static void main(String[] args) {
Thread t1 = new Thread() {
@Override
public void run() {
for (int i=0;i<100;i++) {
System.out.println("t1线程"+i);
}
}
};
Thread t2 = new Thread() {
@Override
public void run() {
for (int i=0;i<100;i++) {
if (i==10){
try {
t1.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("t2线程"+i);
}
}
};
t1.start();
t2.start();
}
}
当t2线程里面i到10时,t1线程加入,t2线程就暂时不走了,知道t1线程走完才继续走t2线程
使用礼让线程可以使多个线程之间更好地协调和共享CPU资源
static void yield()
向调度程序提示当前线程愿意放弃其当前使用的处理器。
调度程序可以自由地忽略此提示。
package 礼让线程;
public class Demo {
public static void main(String[] args) {
Thread t1= new Thread(){
@Override
public void run() {
for (int i=0;i<100;i++) {
System.out.println("t1线程"+i);
Thread.yield();
}
}
};
Thread t2= new Thread(){
@Override
public void run() {
for (int i=0;i<100;i++) {
System.out.println("t2线程"+i);
Thread.yield();
}
}
};
t1.start();
t2.start();
}
}
Java线程可以通过Runnable接口实现数据共享。具体来说,可以将数据定义在Runnable实现类中,并将该实现类作为参数创建Thread对象。在多个线程中,可以共享Runnable实现类中的数据,从而实现数据共享。
示例代码:
package 数据共享;
public class MyRunnable implements Runnable{
int ticket = 100;
@Override
public void run() {
while (true){
if (ticket>0){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+":"+ticket--);
}
}
}
}
package 数据共享;
public class Demo {
public static void main(String[] args) {
MyRunnable myThread = new MyRunnable();
Thread t1=new Thread(myThread,"大麦");
Thread t2=new Thread(myThread,"小麦");
Thread t3=new Thread(myThread,"中麦");
t1.start();
t2.start();
t3.start();
}
}
要注意的是,多个线程同时访问共享变量会存在线程安全的问题,需要进行加锁等操作来保证线程安全
当数据共享时,每条线程都可以对数据进行读取和修改,实现数据共享的同时伴随而来的是数据安全
使用synchronized关键字可以让方法或者代码块在同一时刻只能被一个线程访问,从而保证线程安全。
使用synchronized关键字可以对对象进行加锁,使得在同一时刻只有一个线程能够访问该对象的方法或者代码块。例如:
public synchronized void method() {
// 这里的代码在同一时刻只能被一个线程执行
}
使用synchronized关键字可以对类进行加锁,使得在同一时刻只有一个线程能够访问该类的静态方法或者静态代码块。例如:
public static synchronized void method() {
// 这里的代码在同一时刻只能被一个线程执行
}
除了对整个方法或类进行加锁,还可以使用synchronized关键字对代码块进行加锁,例如:
public void method() {
synchronized (this) {
// 这里的代码在同一时刻只能被一个线程执行
}
}
使用synchronized关键字对静态代码块进行加锁,该锁对该类的所有对象都有效。例如:
public class MyClass {
static {
synchronized (MyClass.class) {
// 这里的代码在同一时刻只能被一个线程执行
}
}
}
package 数据共享;
public class MyRunnable implements Runnable{
int ticket = 100;
@Override
public synchronized void run() {
while (true){
if (ticket>0){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+":"+ticket--);
}
}
}
}
package 数据共享;
public class Demo {
public static void main(String[] args) {
MyRunnable myThread = new MyRunnable();
Thread t1=new Thread(myThread,"大麦");
Thread t2=new Thread(myThread,"小麦");
Thread t3=new Thread(myThread,"中麦");
t1.start();
t2.start();
t3.start();
}
}
需要注意的是,使用synchronized关键字锁定的对象应该是多线程共享的资源,否则会浪费锁的性能。同时,在使用synchronized关键字的时候,也需要考虑是否会造成死锁等问题
使用Lock接口可替代synchronized关键字,Lock接口可以更加灵活地控制锁的粒度,从而提高并发性能。
步骤:
package 数据共享;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyRunnable implements Runnable{
int ticket = 100;
private Lock lock = new ReentrantLock();
@Override
public void run() {
lock.lock();
try {
while (true){
if (ticket>0){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+":"+ticket--);
}
}
} finally {
lock.unlock();
}
}
}
另外,在使用时需要注意锁定的作用域和锁定的粒度,以及可能出现的死锁和饥饿问题。
Java死锁是指两个或多个线程无限期地相互等待对方所持有的资源而无法继续执行。在死锁状态下,每个线程都在等待其他线程释放它所需的资源,从而形成了一种循环依赖,导致程序无法继续执行下去。
代码示例:
package 死锁;
public class Demo {
public static void main(String[] args) {
Object lock1 = new Object();
Object lock2 = new Object();
Thread t1= new Thread(){
@Override
public void run() {
synchronized (lock1) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2) {
System.out.println("我是t1线程");
}
}
}
};
Thread t2= new Thread(){
@Override
public void run() {
synchronized (lock2) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1) {
System.out.println("我是t2线程");
}
}
}
};
t1.start();
t2.start();
}
}
线程1先获取了lock1,然后尝试获取lock2,而线程2先获取了lock2,然后尝试获取lock1。这样,两个线程都拥有了一个锁,然后都在等待对方释放另一个锁,导致死锁。